Template Syntax - Part 2 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 same as the ones that you pass to IFormattable.ToString method implementors. That is, for example, given that d is a DateTime value, you can use the following template to format the value using the “yyyy.MM.dd” pattern.

<<[d]:"yyyy.MM.dd">>

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:

  1. The data band body is duplicated and appended to the report.
  2. 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.

<<foreach ...>>
data_band_body
<</foreach>>

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.

ElementOptional?Remarks
Iteration Variable TypeYesYou 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.
Iteration Variable NameYesYou 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” KeywordNo
Sequence ExpressionNoA sequence expression must return an IEnumerable implementor.

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 an enumeration 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.

TemplateReport
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. For example, given the above declaration of items, you can get a report with their numbered list using the following template.

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 acellMerge 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 you have the Invoice and InvoiceItem classes defined in your application as follows.

public class Invoice
{
	public int Number { get { ... } }
	public IEnumerable<InvoiceItem> Items { get { ... } }
	...
}
public class InvoiceItem
{
	public String Ware { get { ... } }
	public String Pack { get { ... } }
	public int Quantity { get { ... } }
	...
}

Given that invoices is an enumeration of Invoice instances, you could use the following template to output information on several invoices in one table.

#WarePackQuantity
«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.

#WarePackQuantity
11342Natural Mineral WaterBottle 1.0 L30
Bottle 0.5 L50
15385Bottle 1.0 L110

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.

#WarePackQuantity
«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.

#WarePackQuantity
11342Natural Mineral WaterBottle 1.0 L30
Bottle 0.5 L50
15385Natural Mineral WaterBottle 1.0 L110

Numbered Lists

 “1. " in the above 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 you have the Order and Service classes defined in your application as follows.

public class Order
{
    public String ClientName { get { ... } }
    public String ClientAddress { get { ... } }
    public IEnumerable<Service> Services { get { ... } }
    ...
}
 
public class Service
{
    public String Name { get { ... } }
    ...
}

Given that orders is an enumeration of Order instances, 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.

Jane Doe (445 Mount Eden Road Mount Eden Auckland 1024)

   1. Regular Cleaning
   2. Oven Cleaning

John Smith (43 Vogel Street Roslyn Palmerston North 4414)

   3. Regular Cleaning
   4. Oven Cleaning
   5. Carpet Cleaning

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>>

Then, a result document looks as follows.

Jane Doe (445 Mount Eden Road Mount Eden Auckland 1024)

   1. Regular Cleaning
   2. Oven Cleaning

John Smith (43 Vogel Street Roslyn Palmerston North 4414)

   1. Regular Cleaning
   2. Oven Cleaning
   3. Carpet Cleaning

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 `DataSet` instance containing `DataTable` and `DataRelation` objects according to the following data model.

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.

ClientManagerContract Price
<<foreach [c in ds.Contracts]>><<[c.Clients.Name]>><<[c.Managers.Name]>><<[c.Price]>><</foreach>>
Total:<<[ds.Contracts.Sum(c => c.Price)]>>

In this case, the engine produces a report as follows.

ClientManagerContract Price
A CompanyJohn Smith1200000
B Ltd.John Smith750000
C & DJohn Smith350000
E Corp.Tony Anderson650000
F & PartnersTony Anderson550000
G & Co.July James350000
H GroupJuly James250000
I & SonsJuly James100000
J Ent.July James100000
Total:4300000

To populate a document table with a master-detail data, you can use nested table-row data bands like in the following template.

Manager/ClientContract Price
<<foreach [m in ds.Managers]>><<[m.Name]>><<[m.Contracts.Sum(c => c.Price)]>>
<<foreach [c in m.Contracts]>>   <<[c.Clients.Name]>><<[c.Price]>><</foreach>><</foreach>>
Total:<<[ds.Contracts.Sum(c => c.Price)]>>

In this case, the engine produces a report as follows.

Manager/ClientContract Price
John Smith2300000
   A Company1200000
   B Ltd.750000
   C & D350000
Tony Anderson1200000
   E Corp.650000
   F & Partners550000
July James800000
   G & Co.350000
   H Group250000
   I & Sons100000
   J Ent.100000
Total:4300000

You can normally use common data bands nested to table-row data bands as well like in the following template.

ManagerClients
<<foreach [m in ds.Managers]>><<[m.Name]>><<foreach [c in m.Contracts]>><<[c.Clients.Name]>><br/><</foreach>><</foreach>>

In this case, the engine produces a report as follows.

ManagerClients
John SmithA Company
B Ltd.
C & D
Tony AndersonE Corp.
F & Partners
July JamesG & Co.
H Group
I & Sons
J Ent.

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 an enumeration 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
1item1
2item2
3item3

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:

  1. Add a chart to your template at the place where you want it to appear in a result document.

  2. Configure the appearance of the chart.

  3. Add required chart series and configure their appearance as well.

  4. Add a title to the chart, if missing.

  5. Add an opening foreach tag to the chart title.

  6. 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 foreachtag.
      • 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.
  7. 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.

  8. 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.

  9. 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:

  1. A sequence expression declared at the foreach tag is evaluated and iterated.

  2. For every sequence item, expressions declared at x, y, and size tags are evaluated.

  3. Results of these expressions are used to populate corresponding chart series.

  4. All foreach, x, y, and size tags are removed from the chart title and chart series’ names.
    Consider the following example. Assume that you have the Manager and Contract classes defined in your application as follows.

    public class Manager
    {
        public String Name { get { ... } }
        public IEnumerable<Contract> Contracts { get { ... } }
        ...
    }
        
    public class Contract
    {
        public float Price { get { ... } }
        ...
    }
    

    Given that managers is an enumeration of Manager instances, you can use the following template to represent total contract prices achieved by managers in a column chart.

    In this case, the engine produces a report as follows.

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. 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.
  • Predicate stands for Selector returning a Boolean value.

Examples in this table are given using persons and otherPersons, enumerations of instances of the Person class that is defined as follows.

public class Person
{
    public String Name { get { ... } }
    public int Age { get { ... } }
    public IEnumerable<Person> Children { get { ... } }
    ...
}
Extension MethodExamples and Notes
All(Predicate)persons.All(p => p.Age < 50)
Any()persons.Any()
Any(Predicate)persons.Any(p => p.Name == "John Smith")
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.
Single()persons.Single()
Single(Predicate)persons.Single(p => p.Name == "John Smith")
SingleOrDefault()persons.SingleOrDefault()
SingleOrDefault(Predicate)persons.SingleOrDefault(p => p.Name == "John Smith")
Skip(int)persons.Skip(10)
SkipWhile(Predicate)persons.SkipWhile(p => p.Age < 21)
Sum(Selector)persons.Sum(p => p.Children.Count())
The input selector must return a value of any type that has a predefined or user-defined addition operator.
Take(int)persons.Take(5)
TakeWhile(Predicate)persons.TakeWhile(p => p.Age < 50)
Union(IEnumerable)persons.Union(otherPersons)
An implicit reference conversion must exist between types of items of united enumerations.
Where(Predicate)persons.Where(p => p.Age > 18)

© Aspose Pty Ltd 2001-2022. All Rights Reserved.