In this article, we will discuss the behavior of GroupDocs.Assembly APIs.
Overview of the API
Classes of GroupDocs.Assembly for Python via .NET are located within the groupdocs.assembly package, and the data source classes within groupdocs.assembly.data. The main of the classes is DocumentAssembler. All the functionality required to build a report from a template is assembled within the class.
Building Reports
To build a report from a template, you can use the DocumentAssembler.assemble_document method. The following table describes its parameters.
Parameter
Description
source_path or source_stream
A template document: its path, or a binary file object or io.BytesIO to read it from. At runtime, the template is populated with a data from the specified sources and becomes a ready report.
target_path or target_stream
Where to write the report: a path, or a binary file object or io.BytesIO.
load_save_options
Optional. A LoadSaveOptions object, for instance to write the report in another format: LoadSaveOptions(FileFormat.PDF). Passed positionally, it goes before the data sources.
The identifier of the data source within the template. You can omit this identifier, if the template uses the contextual object member access when dealing with the data source.
assemble_document returns True when the report is built. It takes at least one data source.
Given a template to be populated with a data from a list of customers that is identified as “customers” within the template, you can use the following code to build the corresponding report.
Given a template to be populated with a data about a single customer using the contextual object member access, such as <<[CustomerName]>>, you can pass the customer’s dict without a name:
To access XML data while building a report, you can pass an XmlDataSource instance to the engine as a data source. You can also parse XML yourself, with xml.etree.ElementTree for instance, and pass the result as dicts and lists; but then every attribute and text value is a string, unless your code converts it. Thus, it becomes impossible, for example, to use arithmetic operations on numbers or to specify custom date-time and numeric formats to output corresponding values, because all of them are treated as strings.
XmlDataSource overcomes this limitation. Even when XML schema is not provided, XmlDataSource is capable to recognize values of the following types by their string representations:
Int64?
Double?
Boolean?
DateTime?
Note – For recognition of data types to work, string representations of corresponding attributes and text values of XML elements must be formed using invariant culture settings.
Note – The ? means that the values are .NET nullable types. Operators and format strings work on them directly, but a member of the value is reached through Value: <<[Birth.Value.Year]>>. A datetime in a Python dict needs no Value: <<[Birth.Year]>>.
While loading data to XmlDataSource, the engine performs actions typical for XML deserialization behind the scenes: It maps complex-type XML elements to internal objects and simple-type XML elements to fields of containing objects. So, in template documents, an XmlDataSource instance should be treated as an object having corresponding fields and nested objects as shown in the following example.
Name: Amara Osei, Age: 41, Date of Birth: 12.03.1984
Certificates:
Food safety level 3
Forklift operation
Note – To reference a sequence of repeated simple-type XML elements with the same name, the elements’ name itself (for example, “Certificate”) should be used in a template document, whereas the same name with the “_Text” suffix (for example, “Certificate_Text”) should be used to reference the text value of one of these elements.
By default, if a root XML element contains only a sequence of elements of one type, the engine does not generate an internal root object while loading XML data. So, in template documents, such an XmlDataSource instance should be treated as a sequence of corresponding nested objects as shown in the following example.
Name: Amara Osei, Age: 41, Date of Birth: 12.03.1984
Name: Jonas Weber, Age: 37, Date of Birth: 04.07.1988
Name: Lena Park, Age: 29, Date of Birth: 21.11.1996
Average age: 35.666666666666664
However, if your scenario requires an internal object for a root XML element to be always generated while loading data to XmlDataSource, you can force this as shown in the following example. The persons are then a member of the root object, persons.Person:
The following example sums up typical scenarios involving nested complex-type XML elements. It reads managers.xml, Harbor Lane’s account managers and their contracts:
Manager: Mei Tanaka
Contracts:
- Harbourview Hotel ($120000)
- Quayside Bistro ($75000)
- Saltmarsh Deli ($35000)
Manager: Daniel Reyes
Contracts:
- Lantern Street Café ($65000)
- Millbrook Market ($55000)
Manager: Sofia Marino
Contracts:
- Pier Nine Kitchen ($35000)
- The Copper Kettle ($25000)
- Driftwood Grill ($10000)
- Tidewater Tavern ($10000)
Accessing JSON Data
To access JSON data while building a report, you can pass a JsonDataSource instance to the engine as a data source. You can also load JSON with json.load and pass the resulting dicts and lists, as most examples of this documentation do; then Python’s types decide the types of the values, and a date stays a string until your code makes it a datetime.
Using of JsonDataSource enables you to work with typed values of JSON elements in template documents. For more convenience, the set of simple JSON types is extended as follows:
Int64?
Double?
Boolean?
DateTime?
String
Note – Working with complex JSON types (objects and arrays) is also supported.
While loading data to JsonDataSource, the engine performs JSON deserialization and generates corresponding internal objects. So, in template documents, a JsonDataSource instance should be treated according to what a root JSON element represents.
If a root JSON element is an object, a JsonDataSource instance should be treated as an object as well as shown in the following example.
Name: Amara Osei, Age: 41, Date of Birth: 12.03.1984
Certificates:
Food safety level 3
Forklift operation
Note – To reference a JSON object property that is an array of simple-type values, the name of the property (for example, “Certificate”) should be used in a template document, whereas the same name with the “_Text” suffix (for example, “Certificate_Text”) should be used to reference the value of an item of this array.
If a root JSON element is an array, a JsonDataSource instance should be treated as a sequence of items of this array as shown in the following example.
Name: Amara Osei, Age: 41, Date of Birth: 12.03.1984
Name: Jonas Weber, Age: 37, Date of Birth: 04.07.1988
Name: Lena Park, Age: 29, Date of Birth: 21.11.1996
Average age: 35.666666666666664
By default, if a root JSON element is an object having only one property that is an object or array in turn, the engine does not generate an internal root object while loading JSON data. So, in template documents, such a JsonDataSource instance should be treated according to what this property represents instead. For instance, the following JSON snippets can be used to produce the same results in previous examples of this section respectively.
However, if your scenario requires an internal object for a root JSON element to be always generated while loading data to JsonDataSource, you can force this as shown in the following code snippet.
The following example sums up typical scenarios involving nested JSON objects and arrays. It reads managers.json, the same account managers as managers.xml above, with the same template and the same result:
For recognition of JSON simple values (null, boolean, number, integer, and string), the engine provides two modes: loose and strict. In the loose mode, types of JSON simple values are determined upon parsing of their string representations. In the strict mode, types of JSON simple values are determined from JSON notation itself. To see the main difference between the modes, consider the following JSON snippet.
{ "prop": "123" }
In the loose mode, the type of prop is determined as integer, whereas in the strict mode, it is determined as string.
The loose mode is used by the engine by default to support more typed data representation options. However, in some scenarios, it can be more preferable to disable recognition of numbers and other JSON simple values from strings, for example, when you need to keep leading padding zeros in a string value representing a number. In this case, you can switch to the strict mode as shown in the following example. It builds the same product list twice: in the loose mode, the code "00418" is the number 418; in the strict mode, it stays 00418.
fromgroupdocs.assemblyimportDataSourceInfo,DocumentAssemblerfromgroupdocs.assembly.dataimportJsonDataLoadOptions,JsonDataSource,JsonSimpleValueParseModeassembler=DocumentAssembler()# Loose mode (the default): "00418" is read as the number 418assembler.assemble_document("product-codes.docx","product-codes-loose.docx",DataSourceInfo(JsonDataSource("product-codes.json"),"products"))# Strict mode: "00418" stays a stringoptions=JsonDataLoadOptions()options.simple_value_parse_mode=JsonSimpleValueParseMode.STRICTassembler.assemble_document("product-codes.docx","product-codes-strict.docx",DataSourceInfo(JsonDataSource("product-codes.json",options),"products"))
Note – Parsing of date-time values does not depend on whether the loose or strict mode is used.
Recognition of date-time values is a special case, because JSON specification does not define a format for their representation. So, by default, while parsing date-time values from strings, the engine tries several formats in the following order:
All date-time formats supported for the current culture
All date-time formats supported for the English USA culture
All date-time formats supported for the English New Zealand culture
Note – An ISO-8601 date without a time, such as “2015-01-14”, is read as midnight UTC and converted to the local time: on a computer two hours ahead of UTC, measured with 26.11, it becomes 13 January 2015, 22:00. Write the time, “2015-01-14T00:00:00”, to keep the date as it is.
Although this approach is quite flexible, in some scenarios, you may need to restrict strings to be recognized as date-time values. You can achieve this by specifying one or several exact formats in the context of the current culture to be used while parsing date-time values from strings, as a list of strings. In the following example, strings conforming the format “MM/dd/yyyy” (Ordered) are recognized as date-time values while loading JSON, whereas the others (Delivered, already formatted as the report should show it) are not (but see the following note):
In some scenarios, you may need to disable recognition of date-time values at all, for example, when you deal with strings containing already formatted date-time values, which you do not want to re-format using the engine. You can achieve this by setting the exact date-time parse formats to an empty list, options.exact_date_time_parse_formats = [] (but see the following note).
Note – Strings conforming the Microsoft® JSON date-time format (for example, “/Date(1224043200000)/”) are always recognized as date-time values regardless of the exact date-time parse formats.
Accessing CSV Data
To access CSV data while building a report, you can pass a CsvDataSource instance to the engine as a data source.
Using of CsvDataSource enables you to work with typed values rather than just strings in template documents. Although CSV as a format does not define a way to store values of types other than strings, CsvDataSource is capable to recognize values of the following types by their string representations:
Int64?
Double?
Boolean?
DateTime?
Note – For recognition of data types to work, string representations of corresponding values must be formed using invariant culture settings.
In template documents, a CsvDataSource instance should be treated as a sequence of objects having corresponding fields as shown in the following example.
CSV
Amara Osei,41,1984-03-12 9:30:00 am
Jonas Weber,37,1988-07-04 2:00:00 pm
Lena Park,29,1996-11-21 7:45:00 am
Template document
<<foreach [in persons]>>Name: <<[Column1]>>, Age: <<[Column2]>>, Date of Birth: <<[Column3]:"dd.MM.yyyy">>
<</foreach>>
Average age: <<[persons.Average(p => p.Column2)]>>
Name: Amara Osei, Age: 41, Date of Birth: 12.03.1984
Name: Jonas Weber, Age: 37, Date of Birth: 04.07.1988
Name: Lena Park, Age: 29, Date of Birth: 21.11.1996
Average age: 35.666666666666664
By default, CsvDataSource uses column names such as “Column1”, “Column2”, and so on, as you can see from the previous example. However, CsvDataSource can be configured to read column names from the first line of CSV data as shown in the following example.
CSV
Name,Age,Birth
Amara Osei,41,1984-03-12T09:30:00
Jonas Weber,37,1988-07-04T14:00:00
Lena Park,29,1996-11-21T07:45:00
Template document
<<foreach [in persons]>>Name: <<[Name]>>, Age: <<[Age]>>, Date of Birth: <<[Birth]:"dd.MM.yyyy">>
<</foreach>>
Average age: <<[persons.Average(p => p.Age)]>>
Name: Amara Osei, Age: 41, Date of Birth: 12.03.1984
Name: Jonas Weber, Age: 37, Date of Birth: 04.07.1988
Name: Lena Park, Age: 29, Date of Birth: 21.11.1996
Average age: 35.666666666666664
Also, you can use CsvDataLoadOptions to customize the following characters playing special roles while loading CSV data:
Value separator, delimiter (the default is comma)
Single-line comment start, comment_char (the default is sharp)
Quotation mark enabling to use other special characters within a value, quote_char (the default is double quotes)
GroupDocs.Assembly Engine must be aware of the external types that you reference in your template before the engine processes the template. In Python, these are .NET types: the classes of the .NET runtime, such as System.Math or System.Convert, whose static members a template can call. You can set up external types known by the engine through the DocumentAssembler.known_types property. The property represents an unordered set (that is, a collection of unique items) of types. Add a type by its full .NET name, known_types.add("System.Math"), or by a Python type that has a .NET counterpart: known_types.add(float) adds System.Double. Every type in the set must meet requirements declared at Using Types.
Note: Aliases of simple types like int, double, string, and others are known by the engine by default.
Consider the following example. Given a template that rounds the unit price of each order with the static Math.Round method, you can use the following code to make the engine be aware of the System.Math class before processing the template.
A Python class or function cannot be a known type: a template calls .NET code only. Compute such values in Python and put them in the data instead.
Accessing Missing Members of Data Objects
By default, GroupDocs.Assembly forbids access to missing members of data objects used to build a report in template expressions, since such access is forbidden by C# Language Specification 5.0. On attempt to use a missing member of a data object, the assembler throws an exception then, which reaches Python as a RuntimeError: “Can not get the value of member ‘Email’ on type …”.
But in some scenarios, members of data objects are not exactly known while designing a template. For example, if dicts come from a source that leaves a key out when it has no value, some of expected data members can be missing.
Note – For Python data, a member is missing only when no dict of its kind has the key. When some dicts of one list have a key and others do not, or hold None for it, the member exists and is empty (null) where the key is absent.
To support such scenarios, the assembler provides an option to treat missing members of data objects as null literals. You can enable the option as shown in the following example.
Consider the following example. None of the customers of customers.json has an Email key, so by default, the template expression <<[c.Email]>> causes the assembler to throw an exception while building a report. With DocumentAssemblyOptions.ALLOW_MISSING_MEMBERS applied, the assembler treats access to such a member as a null literal, so no exception is thrown and simply no value is written to the report then.
GroupDocs.Assembly Engine uses reflection calls while accessing members of the objects it reads, Python dicts included: a dict reaches the engine as an object of a .NET type made from its keys. However, reflection calls are much slower than direct calls, which creates a performance overhead.
Due to the performance overhead, the engine provides a strategy minimizing the reflection usage. The strategy is based upon the runtime type generation. That is, the engine generates a proxy type per an external type. The proxy directly calls members of the corresponding external type, the engine to access these members in a uniform way with no reflection involved. The proxy is lazily initialized and reused in further. Thus, the reflection is used only while building the proxy.
Although this strategy can significantly minimize the reflection usage in a long run, it creates a performance overhead of the runtime type generation. So, if you deal with small data collections all the time while building your reports, consider the disabling of the strategy.
You can control the enabling of the strategy through the DocumentAssembler.use_reflection_optimization static property. By default, the strategy is enabled.
By default, GroupDocs.Assembly outputs expression results as string values regardless of expression types. In most scenarios, this is the best option, since expression results can be formatted through template syntax depending on expression types. However, for Spreadsheet documents, this may interfere calculation of formulas expecting values of types other than string. To overcome this, GroupDocs.Assembly provides integration with native Spreadsheet data types, which can be enabled as shown in the following code snippet.
Let us show how it works by using an example. Let number be a numeric value. Then, consider the following template for a Spreadsheet cell.
<<[number]>>
By default, while assembling a document, GroupDocs.Assembly converts number into a string using built-in .NET routines. However, when integration with native Spreadsheet data types is enabled, the cell’s value is written as a number, thus enabling to use it in Spreadsheet formulas expecting numbers.
Integration with native Spreadsheet data types also affects formatting of the cell’s value: If a numeric format is defined for the cell (for example, using “Format Cells” context menu in Microsoft Excel), then the format is applied; otherwise, a default numeric format for cells is applied.
Note – The same applies to other native Spreadsheet data types such as date-time and Boolean.
In the following example, the order numbers, quantities and prices of orders.json become numeric cells of the worksheet:
The following table describes, in which cases integration with native Spreadsheet data types has no effect even when enabled.
Cell Template Syntax Example
Explanation
<<[number]:ordinal>>
If an expression result is formatted using template syntax, it is written as a string.
Some text <<[number]>>
Presence of text other than template syntax makes a result cell value to become a string.
<<[number]>><<[anotherNumber]>>
Results of multiple expressions in a single cell are converted to strings and concatenated.
Technical Considerations
Here, we will reveal some technical aspects and implementation details related to the GroupDocs.Assembly Engine which can be useful for you while making design decisions for your applications.
Implicit Enumeration Determination
If you do not specify the type of an enumeration item in a foreach statement or lambda function signature within your template explicitly, the type is implicitly determined by the engine from the type of the enumeration. For the data a Python application passes, it is as follows:
If the enumeration is a list (or tuple) of dicts, then the item type is the type the engine made from the dicts’ keys. All dicts of one list share one type, which has the keys of all of them.
Otherwise, if the enumeration is a list of values of one kind, such as strings or integers, then the item type is that of the values: String, Int64, Double, DateTime and so on.
Otherwise, if the enumeration is a list of values of different kinds, then the item type is Object: the items can be output, but their members cannot be read.
An empty list has no items to take a type from, so its item type is Object, unless the same key holds a non-empty list in another dict of the same kind. A template that reads a member of such an item fails with “Can not get the value of member ‘…’ on type ‘System.Object’”; give the data an item, or apply DocumentAssemblyOptions.ALLOW_MISSING_MEMBERS.
The engine does not cooperate with LINQ providers, and the data a Python application passes is already complete in memory: DataSourceInfo copies a dict and everything it holds when it is created. So, an expression such as the following one runs over all the persons of the city, whatever source they came from.
If you read the data from a database, the query that loaded it is the one that ran; filtering and ordering in the template come after it. In some cases, this can lead to a significant performance overhead. So, if the performance of your application is your primary concern, then consider a preparing of your sequential data outside your templates. That is, pass to the engine a sequential data that is already filtered, ordered, grouped, and so forth: with a WHERE and an ORDER BY in the SQL query, or with a list comprehension and sorted in Python.
Merging GroupDocs.Assembly
The .NET version of this page describes how to merge the GroupDocs.Assembly assembly into another .NET assembly. GroupDocs.Assembly for Python via .NET ships the engine inside its wheel, so there is nothing to merge.
Was this page helpful?
Any additional feedback you'd like to share with us?
Please tell us how we can improve this page.
Thank you for your feedback!
We value your opinion. Your feedback will help us improve our documentation.