This article is the second part of the Template Syntax series of articles. For first part, please visit Template Syntax - Part 1 of 2.
Outputting Expression Results
You can output expression results to your reports using expression tags. An expression tag denotes a placeholder for an expression result within a template. While building a report, the corresponding expression is evaluated, and this placeholder is replaced with the formatted result of the expression.
An expression tag has no name and consists of the following elements:
An expression enclosed by brackets
An optional format string enclosed by double quotes and preceded by the “:” character
An optional html switch
<<[expression]:"format" -html>>
If html switch is not present, the result of the corresponding expression is written to a document as a plain text at runtime. Font attributes are derived from the first character of the corresponding tag in this case.
If html switch is present, the expression result is considered to be a HTML block and is written as such. This feature is useful, when you need to format text parts of an expression result in different ways. For example, the following tag is replaced with a content like “Bold and italic text” at runtime.
<<["<b>Bold</b> and <i>italic</i> text"] -html>>
To format a numeric or date-time expression result, you can specify a format string as an element of the corresponding expression tag. Such format strings are the .NET ones, the same as the ones that you pass to IFormattable.ToString method implementors, not Python’s strftime or format() codes. That is, for example, given that d is a Python datetime, you can use the following template to format the value using the “yyyy.MM.dd” pattern (Python’s "%Y.%m.%d").
<<[d]:"yyyy.MM.dd">>
Likewise, a number takes .NET numeric format strings, such as "0.00" or "N2".
Outputting Sequential Data
You can output a sequence of elements of the same type to your report using a data band. A data band has a body that represents a template for a single element of such a sequence. While building a report, sequence elements are enumerated, and the following procedure takes place for each of the elements:
The data band body is duplicated and appended to the report.
The appended data band body is populated with the element’s data.
Note – A data band body can contain nested data bands.
A data band body is defined between the corresponding opening and closing foreach tags within a template as follows.
You can reference an element of the corresponding sequence in template expressions within a data band body using an iteration variable. At runtime, an iteration variable represents a sequence element for which an iteration is currently being performed. You can declare an iteration variable within the corresponding opening foreach tag.
An opening foreach tag defines a foreach statement enclosed by brackets. The following table describes elements of this statement.
Element
Optional?
Remarks
Iteration Variable Type
Yes
You can specify the type of an iteration variable explicitly. This type must be known by the engine (see Setting up Known External Types for more information). If you do not specify the type explicitly, it is determined implicitly by the engine depending on the type of the corresponding sequence. The type of an item made from a Python dict has no name you can write, so leave the type out for such items.
Iteration Variable Name
Yes
You can specify the name of an iteration variable to use it while accessing the variable’s members. The name must be unique within the scope of the corresponding foreach tag. If you do not specify the name, you can access the variable’s members using the contextual object member access syntax (see Using Contextual Object Member Access for more information).
“in” Keyword
No
Sequence Expression
No
A sequence expression must return an IEnumerable implementor. In Python data, that is a list, a tuple or a set; a DocumentTable and the arrays of a JsonDataSource are sequences as well.
The complete syntax of a foreach tag (including optional elements) is as follows.
<<foreach [variable_type variable_name in sequence_expression]>>
data_band_body
<</foreach>>
Common Data Bands
A common data band is a data band which body starts and ends within paragraphs that belong to a single story or table cell.
In particular, a common data band can be entirely located within a single paragraph. In this case, while building a report, the band is replaced with contents that are entirely located within the same paragraph as well. The following example illustrates such a scenario. Given that items is a Python list of the strings “item1”, “item2”, and “item3”, you can use the following template to enumerate them with commas in a single paragraph.
The items are: <<foreach [item in items]>><<[item]>>, <</foreach>>and others.
In this case, the engine produces a report as follows.
The items are: item1, item2, item3, and others.
When the body of a common data band starts and ends within different paragraphs, the engine duplicates on iteration only those paragraph breaks which are located within the body. The following table illustrates the relevant cases.
Note – Examples in the table are given with paragraph marks shown as per Microsoft Word® editor.
Template
Report
prefix <<foreach [item in items]>><<[item]>>¶ <</foreach>>suffix
prefix item1¶ item2¶ item3¶ suffix
prefix<<foreach [item in items]>>¶ <<[item]>><</foreach>> suffix
prefix¶ item1¶ item2¶ item3 suffix
prefix¶ <<foreach [item in items]>><<[item]>>¶ <</foreach>>suffix
prefix¶ item1¶ item2¶ item3¶ suffix
prefix<<foreach [item in items]>>¶ <<[item]>><</foreach>>¶ suffix
prefix¶ item1¶ item2¶ item3¶ suffix
prefix¶ <<foreach [item in items]>>¶ <<[item]>>¶ <</foreach>>¶ suffix
prefix¶ ¶ item1¶ ¶ item2¶ ¶ item3¶ ¶ suffix
While building a report, duplicated paragraph breaks derive common attributes from their template prototypes. In particular, this fact enables you to build numbered or bulleted lists in reports dynamically (see Numbered Lists).
Merging Table Cells Dynamically
You can merge table cells with equal textual contents within your reports dynamically using cellMerge tags. Syntax of a cellMerge tag is defined as follows.
<<cellMerge>>
By default, a cellMerge tag causes a cell merging operation only in a vertical direction during runtime. However, you can alter this behavior in the following ways:
To merge cells only in a horizontal direction, use the horz switch as follows.
<<cellMerge -horz>>
To merge cells in both – vertical and horizontal – directions, use the both switch as follows.
<<cellMerge -both>>
For two or more successive table cells to be merged dynamically in either direction by the engine, the following requirements must be met:
Each of the cells must contain a cellMerge tag denoting a cell merging operation in the same direction (or directions).
Each of the cells must not be already merged in another direction. This requirement does not apply when a both switch is used.
The cells must have equal textual contents ignoring leading and trailing whitespaces.
Consider the following template.
…
…
…
…
«cellMerge»«[value1]»
…
…
«cellMerge»«[value2]»
…
…
…
…
If value1 and value2 have the same value, say “Hello”, table cells containing cellMerge tags are successfully merged during runtime and a result report looks as follows then.
...
...
...
...
Hello
...
...
...
...
...
...
If value1 and value2 have different values, say “Hello” and “World”, table cells containing cellMerge tags are not merged during runtime and a result report looks as follows then.
…
…
…
…
Hello
…
…
World
…
…
…
…
Note – A cellMerge tag can be normally used within a table data band.
You can define an additional restriction on dynamic merging of table cells by providing an expression for a cellMerge tag using the following syntax.
<<cellMerge [expression]>>
During runtime, expressions defined for cellMerge tags are evaluated and dynamic cell merging is discarded for those tags, which expressions return unequal values, even if all other conditions for merging such as equal cell textual contents are met. In particular, this feature is useful when cells corresponding to different data band sequence elements should not be merged as shown in the following example.
Assume that invoices is a list of invoices, each a dict with a Number and the Items it lists, each item a dict with a Ware, a Pack and a Quantity:
You could use the following template to output information on several invoices in one table.
#
Ware
Pack
Quantity
«foreach [invoice in invoices]»«foreach [item in invoice.Items]»«[invoice.Number]»«cellMerge»
«[item.Ware]»«cellMerge»
«[item.Pack]»
«[item.Quantity]»«/foreach»«/foreach»
A result document would look as follows then.
#
Ware
Pack
Quantity
11342
Cold-pressed olive oil
Bottle 1.0 L
30
Bottle 0.5 L
50
15385
Bottle 1.0 L
110
That is, cells corresponding to the same wares at different invoices would be merged, which is unwanted. To prevent this from happening, you can use the following template instead.
#
Ware
Pack
Quantity
«foreach [invoice in invoices]»«foreach [item in invoice.Items]»«[invoice.Number]»«cellMerge»
«[item.Ware]»«cellMerge [invoice.IndexOf()]»
«[item.Pack]»
«[item.Quantity]»«/foreach»«/foreach»
Then, a result document looks as follows.
#
Ware
Pack
Quantity
11342
Cold-pressed olive oil
Bottle 1.0 L
30
Bottle 0.5 L
50
15385
Cold-pressed olive oil
Bottle 1.0 L
110
Numbered Lists
Given the above declaration of items, you can get a report with their numbered list using the following template. “1. " in the template stands for a numbered list label.
1. <<foreach [item in items]>><<[item]>>
<</foreach>>
In this case, the engine produces a report as follows.
1. item1
2. item2
3. item3
Dynamic list numbering restart
This feature is useful when working with a nested numbered list within a data band as shown in the following example.
Assume that orders is a list of orders, each a dict with a ClientName, a ClientAddress and the Services ordered, each service a dict with a Name:
orders=[{"ClientName":"Quayside Bistro","ClientAddress":"3 Harbour Steps, Port Ellery","Services":[{"Name":"Next-day delivery"},{"Name":"Menu pairing advice"}]},{"ClientName":"Saltmarsh Deli","ClientAddress":"7 Marsh Lane, Port Ellery","Services":[{"Name":"Next-day delivery"},{"Name":"Cold-chain storage"},{"Name":"Shelf restocking"}]},]
You could try to use the following template to output information on several orders in one document.
<<foreach [order in orders]>><<[order.ClientName]>> (<<[order.ClientAddress]>>)
1. <<foreach [service in order.Services]>><<[service.Name]>>
<</foreach>><</foreach>>
But then, a result document would look as follows.
Quayside Bistro (3 Harbour Steps, Port Ellery)
1. Next-day delivery
2. Menu pairing advice
Saltmarsh Deli (7 Marsh Lane, Port Ellery)
3. Next-day delivery
4. Cold-chain storage
5. Shelf restocking
That is, there would be a single numbered list across all orders, which is not applicable for this scenario. However, you can make list numbering to restart for every order by putting a restartNum tag into your template before a corresponding foreach tag as follows.
<<foreach [order in orders]>><<[order.ClientName]>> (<<[order.ClientAddress]>>)
1. <<restartNum>><<foreach [service in order.Services]>><<[service.Name]>>
<</foreach>><</foreach>>
When using with a data band, it is required to put a restartNum tag before a corresponding foreach tag in the same numbered paragraph.
Then, a result document looks as follows.
Quayside Bistro (3 Harbour Steps, Port Ellery)
1. Next-day delivery
2. Menu pairing advice
Saltmarsh Deli (7 Marsh Lane, Port Ellery)
1. Next-day delivery
2. Cold-chain storage
3. Shelf restocking
You can use a restartNum tag without a data band to dynamically restart list numbering for a containing paragraph, for example, the tag can be used to restart list numbering for a document inserted dynamically.
Table-Row Data Bands
A table-row data band is a data band which body occupies single or multiple rows of a single document table. The body of such a band starts at the beginning of the first occupied row and ends at the end of the last occupied row as follows.
<<foreach ...>> …
…
…
…
…
…
…
…
… <</foreach>>
The following examples in this section are given using ds, a DocumentTableSet loaded from related-tables.xlsx: the worksheets MANAGER, CLIENT and CONTRACT of Harbor Lane’s account managers, their clients and their contracts, related through their ID columns. Each contract row has a parent manager row and a parent client row, so a template reads c.Manager.Name for a contract c, and m.Contract for the contracts of a manager m. Table and field names are case-insensitive. Each example loads the tables with the same code:
fromgroupdocs.assembly.dataimportDocumentTableOptions,DocumentTableSetdefload_table(args):# The first row of every worksheet holds its column namesoptions=DocumentTableOptions()options.first_row_contains_column_names=Trueargs.options=optionsds=DocumentTableSet("related-tables.xlsx",load_table)ds.relations.add(ds.tables["CLIENT"].columns["ID"],ds.tables["CONTRACT"].columns["CLIENT_ID"])ds.relations.add(ds.tables["MANAGER"].columns["ID"],ds.tables["CONTRACT"].columns["MANAGER_ID"])
The most common use case of a table-row data band is the building of a document table that represents a list of items. You can use a template like the following one to achieve this.
Client
Manager
Contract Price
<<foreach [c in ds.Contract]>><<[c.Client.Name]>>
<<[c.Manager.Name]>>
<<[c.Price]>><</foreach>>
Total:
<<[ds.Contract.Sum(c => c.Price)]>>
fromgroupdocs.assemblyimportDataSourceInfo,DocumentAssemblerfromgroupdocs.assembly.dataimportDocumentTableOptions,DocumentTableSetdefload_table(args):# The first row of every worksheet holds its column namesoptions=DocumentTableOptions()options.first_row_contains_column_names=Trueargs.options=optionsds=DocumentTableSet("related-tables.xlsx",load_table)ds.relations.add(ds.tables["CLIENT"].columns["ID"],ds.tables["CONTRACT"].columns["CLIENT_ID"])ds.relations.add(ds.tables["MANAGER"].columns["ID"],ds.tables["CONTRACT"].columns["MANAGER_ID"])assembler=DocumentAssembler()assembler.assemble_document("contract-list.docx","contract-list-report.docx",DataSourceInfo(ds,"ds"))
In this case, the engine produces a report as follows.
Client
Manager
Contract Price
Harbourview Hotel
Mei Tanaka
120000
Quayside Bistro
Mei Tanaka
75000
Saltmarsh Deli
Mei Tanaka
35000
Lantern Street Café
Daniel Reyes
65000
Millbrook Market
Daniel Reyes
55000
Pier Nine Kitchen
Sofia Marino
35000
The Copper Kettle
Sofia Marino
25000
Driftwood Grill
Sofia Marino
10000
Tidewater Tavern
Sofia Marino
10000
Total:
430000
To populate a document table with a master-detail data, you can use nested table-row data bands like in the following template.
Manager/Client
Contract Price
<<foreach [m in ds.Manager]>><<[m.Name]>>
<<[m.Contract.Sum(c => c.Price)]>>
<<foreach [c in m.Contract]>>- <<[c.Client.Name]>>
<<[c.Price]>><</foreach>><</foreach>>
Total:
<<[ds.Contract.Sum(c => c.Price)]>>
fromgroupdocs.assemblyimportDataSourceInfo,DocumentAssemblerfromgroupdocs.assembly.dataimportDocumentTableOptions,DocumentTableSetdefload_table(args):# The first row of every worksheet holds its column namesoptions=DocumentTableOptions()options.first_row_contains_column_names=Trueargs.options=optionsds=DocumentTableSet("related-tables.xlsx",load_table)ds.relations.add(ds.tables["CLIENT"].columns["ID"],ds.tables["CONTRACT"].columns["CLIENT_ID"])ds.relations.add(ds.tables["MANAGER"].columns["ID"],ds.tables["CONTRACT"].columns["MANAGER_ID"])assembler=DocumentAssembler()assembler.assemble_document("manager-contract-totals.docx","manager-contract-totals-report.docx",DataSourceInfo(ds,"ds"))
fromgroupdocs.assemblyimportDataSourceInfo,DocumentAssemblerfromgroupdocs.assembly.dataimportDocumentTableOptions,DocumentTableSetdefload_table(args):# The first row of every worksheet holds its column namesoptions=DocumentTableOptions()options.first_row_contains_column_names=Trueargs.options=optionsds=DocumentTableSet("related-tables.xlsx",load_table)ds.relations.add(ds.tables["CLIENT"].columns["ID"],ds.tables["CONTRACT"].columns["CLIENT_ID"])ds.relations.add(ds.tables["MANAGER"].columns["ID"],ds.tables["CONTRACT"].columns["MANAGER_ID"])assembler=DocumentAssembler()assembler.assemble_document("manager-clients.docx","manager-clients-report.docx",DataSourceInfo(ds,"ds"))
In this case, the engine produces a report as follows.
Manager
Clients
Mei Tanaka
Harbourview Hotel, Quayside Bistro, Saltmarsh Deli
Daniel Reyes
Lantern Street Café, Millbrook Market
Sofia Marino
Pier Nine Kitchen, The Copper Kettle, Driftwood Grill, Tidewater Tavern
The same templates work with Python data: given that ds is a dict whose Contract key holds a list of contract dicts, each with a Client dict, a Manager dict and a Price, and whose Manager key holds the managers, each with its Contract list, ds.Contract and m.Contract are lists, and c.Manager.Name a member of a nested dict.
Extension Methods of Iteration Variables
GroupDocs.Assembly Engine provides special extension methods for iteration variables of any type. You can normally use these extension methods in template expressions. The following list describes the extension methods.
IndexOf()
Returns the zero-based index of a sequence item that is represented by the corresponding iteration variable. You can use this extension method to distinguish sequence items with different indexes and then handle them in different ways. For example, given that items is a Python list of the strings “item1”, “item2”, and “item3”, you can use the following template to enumerate them prefixing all of them but the first one with commas.
The items are: <<foreach [
item in items]>><<[item.IndexOf() != 0
? ", "
: ""]>><<[item]>><</foreach>>.
In this case, the engine produces a report as follows.
The items are: item1, item2, item3.
NumberOf()
Returns the one-based index of a sequence item that is represented by the corresponding iteration variable. You can use this extension method to number sequence items without involving Microsoft Word® lists. For example, given the previous declaration of items, you can enumerate and number them in a document table using the following template.
No.
Item
<<foreach [item in items]>><<[item.NumberOf()]>>
<<[item]>><</foreach>>
In this case, the engine produces a report as follows.
No.
Item
1
item1
2
item2
3
item3
Charts Representing Sequential Data
GroupDocs.Assembly Engine enables you to use charts to represent your sequential data. To declare a chart that is going to be populated with data dynamically within your template, do the following steps:
Add a chart to your template at the place where you want it to appear in a result document.
Configure the appearance of the chart.
Add required chart series and configure their appearance as well.
Add a title to the chart, if missing.
Add an opening foreach tag to the chart title.
Depending on the type of the chart, add x tags to the chart title or chart series’ names as follows.
<<x [x_value_expression]>>
For a scatter or bubble chart, you can go one of the following ways:
To use the same x-value expression for all chart series, add a single x tag to the chart title after the corresponding foreach tag.
To use different x-value expressions for every chart series, add multiple x tags to chart series’ names – one for each chart series. An x-value expression for a scatter or bubble chart must return a numeric value.
For a chart of another type, add a single x tag to the chart title after the corresponding foreach tag. In this case, an x-value expression must return a numeric, date, or string value.
For a chart of any type, add y tags to chart series’ names as follows.
<<y [y_value_expression]>>
An y-value expression must return a numeric value.
For a bubble chart, add size tags to chart series’ names as follows.
<<size [bubble_size_expression]>>
A bubble-size expression must return a numeric value.
For a chart with dynamic data, you can select which series to include into it dynamically based upon conditions. In particular, this feature is useful when you need to restrict access to sensitive data in chart series for some users of your application. To use the feature, do the following steps:
Declare a chart with dynamic data in the usual way
For series to be removed from the chart based upon conditions dynamically, define the conditions in names of these series using removeif tags having the following syntax
<<removeif [conditional_expression]>>
A conditional expression must return a Boolean value.
Note: A closing foreach tag is not used for a chart.
While composing expressions for x, y, and size tags, you can normally reference an iteration variable declared at the corresponding foreach tag in a chart title in the same way as if you intended to output results of expressions within a data band.
Note: You can normally use charts with dynamic data within data bands.
During runtime, a chart with a foreach tag in its title is processed by the engine as follows:
A sequence expression declared at the foreach tag is evaluated and iterated.
For every sequence item, expressions declared at x, y, and size tags are evaluated.
Results of these expressions are used to populate corresponding chart series.
All foreach, x, y, and size tags are removed from the chart title and chart series’ names.
Consider the following example. Assume that managers is a list of managers, each a dict with a Name and its Contract list, each contract a dict with a Price, as managers.json holds them. You can represent the total contract prices achieved by managers in a column chart whose title holds <<foreach [m in managers]>><<x [m.Name]>> and whose series name holds <<y [m.Contract.Sum(c => c.Price)]>>. The chart pages show such templates and their reports: see Working with Chart Series Coloring and Working with Chart Reports - Filtered Ordered Grouped.
Enumeration Extension Methods
The Enumeration
GroupDocs.Assembly Engine enables you to perform common manipulations on a sequential data through the engine’s built-in extension methods for IEnumerable, which apply to Python lists as they do to .NET collections. These extension methods mimic some extension methods of IEnumerable providing the same signatures and behavior features. Thus, you can group, sort, and perform other sequential data manipulations in template expressions in a familiar way.
The Enumeration Extension
Below table describes the built-in extension methods. The following notation conventions are used within the table:
Selector stands for a lambda function returning a value and taking an enumeration item as its single argument. See Using Lambda Functions for more information.
ComparableSelector stands for Selector returning IComparable: a string, a number or a date-time value, for instance.
Predicate stands for Selector returning a Boolean value.
Examples in this table are given using persons and otherPersons, lists of dicts each with a Name (str), an Age (int) and Children (a list of such dicts). Dicts compare by value: Distinct, GroupBy and Contains treat two dicts of one list with equal values as equal items.
Note – Two lists of Python data have item types of their own, even when their dicts have the same keys, unless they hold at least one dict object in common. So persons.Concat(otherPersons) and persons.Union(otherPersons) fail with “Can not resolve method ‘Concat’” when the two lists were loaded separately (by json.load, for instance), and persons.Contains(...) never finds an item of otherPersons. They work when the lists share a dict, as persons = [amara, jonas] and otherPersons = [lena, amara] do, or keep the persons in one list and select from it with Where.
Extension Method
Examples and Notes
All(Predicate)
persons.All(p => p.Age < 50)
Any()
persons.Any()
Any(Predicate)
persons.Any(p => p.Name == "Amara Osei")
Average(Selector)
persons.Average(p => p.Age) The input selector must return a value of any type that has predefined or user-defined addition and division operators.
Concat(IEnumerable)
persons.Concat(otherPersons) An implicit reference conversion must exist between types of items of concatenated enumerations.
Contains(Object)
persons.Contains(otherPersons.First())
Count()
persons.Count()
Count(Predicate)
persons.Count(p => p.Age > 30)
Distinct()
persons.Distinct()
First()
persons.First()
First(Predicate)
persons.First(p => p.Age > 30)
FirstOrDefault()
persons.FirstOrDefault()
FirstOrDefault(Predicate)
persons.FirstOrDefault(p => p.Age > 30)
GroupBy(Selector)
persons.GroupBy(p => p.Age) Or persons.GroupBy(p => new { Age = p.Age, Count = p.Children.Count() }) This method returns an enumeration of group objects. Each group has a unique key defined by the input selector and contains items of the source enumeration associated with this key. You can access the key of a group instance using the Key property. You can treat a group itself as an enumeration of items that the group contains.
Last()
persons.Last()
Last(Predicate)
persons.Last(p => p.Age > 100)
LastOrDefault()
persons.LastOrDefault()
LastOrDefault(Predicate)
persons.LastOrDefault(p => p.Age > 100)
Max(ComparableSelector)
persons.Max(p => p.Age)
Min(ComparableSelector)
persons.Min(p => p.Age)
OrderBy(ComparableSelector)
persons.OrderBy(p => p.Age) Or persons.OrderBy(p => p.Age).ThenByDescending(p => p.Name) Or persons.OrderBy(p => p.Age).ThenByDescending(p => p.Name).ThenBy(p => p.Children.Count()) This method returns an enumeration ordered by a single key. To specify additional ordering keys, you can use the following extension methods of an ordered enumeration:
ThenBy(ComparableSelector)
ThenByDescending(ComparableSelector)
OrderByDescending(ComparableSelector)
persons.OrderByDescending(p => p.Age) Or persons.OrderByDescending(p => p.Age).ThenByDescending(p => p.Name) Or persons.OrderByDescending(p => p.Age).ThenByDescending(p => p.Name).ThenBy(p => p.Children.Count()) See the previous note.