Using Zen Reports
Gathering Zen Report Data
[Home] [Back] [Next]
InterSystems: The power behind what matters   
Class Reference   
Search:    

The primary way to gather data for a Zen report is to provide an XData ReportDefinition block in the Zen report class. XData ReportDefinition specifies the data to acquire from the Caché database and describes how to format this data as XML. This XML becomes the source data for the Zen report.

The next chapter, Formatting Zen Report Pages,” describes how to generate the XSLT transformations that render the XML data for display in HTML or PDF format. This chapter describes how to format the source data as XML so that it can be input to an XSLT transformation.
Topics include:
The following table lists techniques you can use to generate the XML data source for a Zen report.
Technique How to Use It to Generate an XML Data Source For More Information
XData ReportDefinition Write an XData ReportDefinition block in a Zen report class. This generates an XML data source when you run the report. XData ReportDefinition in this chapter
DATASOURCE
Provide a value for the DATASOURCE class parameter. DATASOURCE references an XML file that contains the data for the Zen report.
If you provide a DATASOURCE value in the Zen report class, or the equivalent $DATASOURCE parameter in the URI when invoking the Zen report from a browser, you can omit the XData ReportDefinition block from your Zen report class. If you provide both, the DATASOURCE value takes precedence and Zen ignores any XData ReportDefinition that you provide.
DATASOURCE in this chapter
Full WRITE
Write a class method in the language of your choice that writes out XML statements that comprise the complete source data for the report. Reference this method from the XData ReportDefinition block in the Zen report class using the call and callClass attributes on the top level <report> element.
In this case, your XData ReportDefinition block consists of a single <report> element that provides call and (optionally) callClass attributes.
Writing XML Statements From a Class Method in this chapter
Partial WRITE Write a class method in the language of your choice that writes out a block of XML statements to use at a specific location within the source data for the report. Reference this method from the XData ReportDefinition block in the Zen report class using the call and callClass attributes on a <group> element. Writing XML Statements From a Class Method in this chapter
<call> The <call> element calls a method that returns a stream, and inserts the stream into the report definition at the place where the call element occurs. The <call> section in this chapter
<callelement> Similar to <call>, but providing awareness of the data context where it is used. The <callelement> section in this chapter
<include> Bring into the XML data source a block of XML statements that exists in an XData block. This XData can be in the same Zen report class or in some other class. The <include> section in this chapter
<macrodef> Bring ReportDefinition building-blocks into the ReportDefinition from an XData block in the same Zen report class or in some other class. The <macrodef> section in this chapter
<get> Bring into the XML data source a block of XML statements that has been generated by the XData ReportDefinition block in some other Zen report class. The <get> section in this chapter
Class query Generate a complete Zen report, including its XML data source, by asking the Zen report generator to generate a Zen report class from an existing ObjectScript class that has a query defined. The class query contributes to the XData ReportDefinition block in the generated Zen report class. Generating a Zen Report from a Class Query in this chapter
Several techniques are also available for gathering data in the ReportDisplay block, see the section Gathering Data in the ReportDisplay Block.”
XData ReportDefinition
An XData ReportDefinition block may contain the following syntax elements:
In expressions used by these syntax elements, the %val variable represents a field from the current query.
The following figure shows the relationship between elements in the XData ReportDefinition block and the resulting XML representation of the data. On the left is the ReportDefinition block from ZenApp.MyReport in the SAMPLES database. On the right, is the XML generated by this ReportDefinition. A detailed explanation follows the figure.
XData ReportDefinition Statements and the Resulting XML Output
The %val Variable
%val is a special variable that you can use only in an XData ReportDefinition block. All XData ReportDefinition elements support %val in attributes whose values are ObjectScript expressions: expression, breakOnExpression, and filter are the primary examples.
%val can be single-valued or multidimensional, as the following topics explain.
Note:
A general knowledge of ObjectScript is helpful in knowing how to construct these expressions. See the book Using Caché ObjectScript, particularly the String Relational Operators section in the chapter Operators and Expressions.”
Where %val is Supported
You can use %val in the expression attribute for the value nodes <element>, <attribute>, or <aggregate>. %val represents the value of the field from the field attribute in the same element. You can see this in the following <element> example:
<element name="displayURL" field="ID" expression="..GetDisplayURL(%val)"/>
Or in <attribute>:
<attribute name="name" field="SalesRep"
  expression='$E(%val)_$ZCVT($E(%val,2,$l(%val)),"L")'/>
Because the value of the expression attribute is an ObjectScript expression, the previous example can use the ObjectScript functions such as $EXTRACT ($E) or $ZCONVERT ($ZCVT) to format the data contained in the resultset field called SalesRep.
<report> and <group> support %val in breakOnExpression to represent the value of the breakOnField field.
<report>, <group>, <element>, and <attribute> support %val in filter expressions. In this case %val represents the value of a field from the fields attribute in the same element, and may be multidimensional rather than single-valued.
%val also works inside the ObjectScript code for custom aggregate classes.
Multidimensional Values of %val
Anywhere you can use single valued %val, you can also use its multidimensional form:
%val("CaseSensitiveFieldName")
To do this for value nodes <element> or <attribute>, place a comma-separated list of field names in the fields (not field) attribute. Then refer to these values subscripted by their field name in the expression or filter attributes. These subscripts are case sensitive.
The following example references two query fields in the fields attribute, and then uses these field names as subscripts for %val in the value of the expression attribute. In this example, the expression value is an ObjectScript expression that uses the ObjectScript _ (underscore) concatenation operator for strings:
<element name="message" fields="Customer,SaleDate"
         expression='%val("Customer")_" has date "_%val("SaleDate")'/>
<report> and <group> also support multidimensional %val. The <report> or <group> must include a fields attribute for this feature to work.
Use quoting conventions carefully. The expression value above uses double quotes effectively inside single quotes. The following example applies correct quoting conventions for %val subscripts and the letter G inside the single quote characters used to contain the filter value.
filter = '$E(%val(""TheaterName""))=""G""'
As an alternative to careful use of quotes in expressions like these, and to provide greater modularity and flexibility in your Zen report classes, attributes such as expression, breakOnExpression, or filter attribute allow you to supply a reference to a class method as the value of the attribute. Then you can place the logic inside that method instead of inside the expression attribute. The following is an example of this practice:
filter="..Filter()"
The previous syntax works when the Zen report class also defines the method being referenced in the expression, and the method is defined as returning a zero or non-zero value, as in:
Method Filter()
{
    If $E(%val("TheaterName"))="G" Quit 1
    Quit 0
}
Suppose instead you defined your method as follows:
Method Filter(input As %String)
{
    If $E(input)="G" Quit 1
    Quit 0
}
Then you could set the filter as follows. Note the quoting conventions for the %val subscript:
filter='..Filter(%val(""TheaterName""))'
<report> and <group>
The <report> element is the required top level container within an XData ReportDefinition block.
Important:
Different <report> and <group> elements are used in an XData ReportDisplay block. For details, see Formatting Zen Report Pages.”
A <report> contains zero or more <group> elements to organize the data for the report.
When a report contains nested groups, each contained <group> is called a child of the <group> that contains it; the containing <group> is called the parent of the <group> elements that it contains. Multiple <group> elements may exist at each level of nesting, except at the top level, where there is only one <report> element. Any <group> elements that are contained within the same parent <group>, at the same level, are called siblings.
Within an XData ReportDefinition block, the syntax rules for <report> and <group> are:
<report> and <group> Attributes
<report> and <group> have the general-purpose attributes listed in the following table.
Attribute Description
Query attributes These attributes help you acquire the source data for the report. See the section Building the <report> or <group> Query following the table.
Break on attributes The breakOnExpression and breakOnField attributes help you organize the source data for the report. See the section Break On Field or Expression following the table.
Direct XML attributes The call and callClass attributes help you specify the source data by writing XML statements from a class method instead of generating them with your <report> or <group> definition. See the section Writing an XML Data Source.”
call See Direct XML Attributes.
callClass See Direct XML Attributes.
excelSheetName
The excelSheetName attribute lets you specify a name for the generated worksheet. By default, the worksheet uses Excel's default naming convention for sheets: "Sheet1", "Sheet2" and so on. Supply excelSheetName on <report> for a single sheet report, and on each <group> that defines a worksheet for a multi-sheet report. The excelSheetName attribute supports localization. See Localizing Zen Reports.
If excelSheetName begins with a ! (exclamation point) the report interprets what follows as a ObjectScript runtime expression that is evaluated to get the sheet name. Sheet names supplied by runtime expressions are not localized.
For further control over sheet name generation, you can override the method %getUniqueExcelSheetName, defined in %ZEN.Report.reportPage. See Multi-sheet Reports.”
getxmlstylesheet The name of a method that returns a stream that contains the contents of an XSLT style sheet. This style sheet is used to perform transformations on the XML provided by the ReportDefinition block prior to processing by the ReportDisplay block. See Restructuring the ReportDefinition XML.” This attribute is available only for <report> element.
name
The <report> or <group> generates an XML element of this name in the output.
If the supplied name is an invalid string for use as an XML identifier, the report does not work correctly. The most obvious characters to avoid are any white space characters, plus the five standard XML entity characters &'<>"
If the group is a <report> the name attribute is required. For a <group> the name is optional and the <group> generates a name for itself.
suppressExcelHeaders
The suppressExcelHeaders attribute lets you suppress all headers that are normally generated when you create an Excel spreadsheet from a Zen report. Specify suppressExcelHeaders="true" on <report> to suppress all headers, and on <group> to suppress headers for the associated worksheet in a multi-sheet report.
This attribute has the underlying data type %ZEN.Datatype.boolean. See Zen Reports Attribute Data Types.”
xmlstylesheet A stream that contains the contents of an XSLT style sheet. This style sheet is used to perform transformations on the XML provided by the ReportDefinition block prior to processing by the ReportDisplay block. See Restructuring the ReportDefinition XML.” This attribute is available only for <report> element.
xmlstylesheetarg An optional argument to the method specified by getxmlstylesheet .
Building the <report> or <group> Query
A <report> or <group> requires a resultset to generate data. To obtain this resultset, a <report> or <group> can generate its own resultset by specifying a query. A <group> can also inherit the resultset generated by one of its ancestor <report> or <group> elements.
Query Attributes for Gathering Data
A <report> or <group> supports the following attributes for specifying a query.
Attribute Description
fields
fields consists of a comma-separated list of one or more of the field names from the resultset query.
White space in the fields value is acceptable. For filter syntax details, including the %val variable, see the filter entry in this table.
For additional information, see the discussion following this table.
filter
ObjectScript expression that determines whether or not the resultset row currently being processed should be included in the XML output for this <report> or <group>. When the expression evaluates to 0, skip that resultset row.
In the filter expression, you can refer to the values of fields from the resultset query using the %val variable, subscripted with the case sensitive names listed in the fields attribute for this <report> or <group>. For syntax details, see the The %val Variable section.
A general knowledge of ObjectScript is helpful in knowing how to construct these expressions. In addition to the ObjectScript tips in The %val Variable section, see Using Caché ObjectScript, particularly the String Relational Operators section in the chapter Operators and Expressions.”
OnCreateResultSet The name of a server-side callback method to call to create a %ResultSet object. The OnCreateResultSet value must be the name of a server-only class method defined in the Zen report class. For details, see The OnCreateResultSet Callback Method.”
orderby
A comma-separated list of fields used to override any ORDER BY phrase that is already present in the query for this <report> or <group>. If the first character in the orderby string is a ! (exclamation point) then Zen reports interpret the remainder of the string as an ObjectScript expression that provides the string. For further details, see The orderby Attribute in ReportDefinition following this table. For information on using orderby in the ReportDisplay section of a report, see The orderby Attribute in ReportDisplay in the chapter Displaying Zen Report Data.
queryClass
The name of the class containing the query. This attribute is used only if you also provide a value for queryName.
The section Referencing a Class Query in the “Zen Tables” chapter of Using Zen Components provides information on using queryClass in a Zen page.
queryName
The name of the class query that provides the %ResultSet. The class query must be projected as SqlProc. If you do not also provide a value for queryClass, the report assumes that the query is defined in the current report class.
The section Referencing a Class Query in the “Zen Tables” chapter of Using Zen Components provides information on using queryName in a Zen page.
removeEmpty
In the XData ReportDefinition block, the removeEmpty attribute controls whether or not blank values are included in the XML data generated by <report> or <group> elements. That is, if the query returns a resultset with one or more empty rows, removeEmpty determines how to handle those rows. If removeEmpty is:
  • Not specified, the <group> inherits the removeEmpty value of its parent. This is the default for any group that is not a <report>. The default removeEmpty value for a <report> is false.
  • false, empty fields are retained in the group, and are output to the generated XML description of the data for the report. This is the default for a <report>, because <report> is at the top level, so there is no parent to supply an inherited value for removeEmpty.
  • true, empty fields are omitted from the group, and are not written to the generated XML description of the data for the report.
Not supported for Excel or xlsx output.
runonce
When a <report> or <group> has its runonce attribute set to true, the <report> or <group> itself has no query. Instead, the <report> or <group> serves as a container for other groups that may each have their own query defined.
If you supply a runonce attribute for a <report> or <group>, Zen ignores sql, queryClass, or any other query attributes that you supply for that <report> or <group>. Subgroups within that container may have query attributes defined.
runtimeMode
SQL runtime mode for the query to be executed to fetch the results for this report. Possible values are:
  • 0 for LOGICAL mode
  • 1 for ODBC mode
  • 2 for DISPLAY mode
The default runtimeMode of 2 (DISPLAY mode) is appropriate for most cases and usually does not need to be changed.
For more information about setting the SQL runtime mode for a query that returns a %ResultSet, see Queries Invoking User-defined Functions in the “Querying the Database” chapter of Using Caché SQL.
sql
Server-side SQL query to get contents of the <report> or <group> list. For additional information, see the discussion following this table.
suppressRootTag suppressRootTag suppresses generation of the report root tag, which is otherwise generated from the name attribute of the report. Suppressing the root tag is useful if the report derives its XML solely through DATASOURCE, or the <include> or <call> elements, and the injected XML includes its own root tag.
sqlexpression
sqlexpression allows you to use a COS expression to provide an SQL query for the <table> or <group>. Note that Zen reports does not parse the resulting SQL at compile time. Studio does not assist you in creating the attribute value, as it does for the sql attribute. For more information, see The sqlexpression Property.
top
Positive integer value. Has the same effect as a "SELECT TOP top" phrase in an SQL query, for example, "SELECT TOP 10". Causes the <group> or <report> to be limited to the number of results specified by top.
The following example shows a fields attribute that selects two fields from the resultset returned by the sql attribute.
<report
xmlns="http://www.intersystems.com/zen/report/definition"
name="fieldsTest" sql="Select Name, SSN, DOB from
Sample.Person WHERE Name > 'm'" fields="Name, SSN"
filter='%val(""Name"")=""Vonnegut,Agnes M.""'>
</report>
The sql attribute works in Zen reports the same way as it does in Zen <tablePane>, except that all Zen queries run in display mode and Zen reports support the runtimeMode attribute to control the runtime mode.
Because sql is used within an XML block, its value must conform to XML syntax rules. For example, you cannot use the < (less-than) character in comparisons; you must substitute the XML entity &lt; for < as in:
sql="SELECT ID,Customer,Num,SalesRep,SaleDate 
  FROM ZENApp_Report.Invoice 
  WHERE (ID &lt; 500)
  ORDER BY SalesRep,SaleDate"
Note that /* */ is the only comment syntax supported in the sql string.
For details and examples using sql and query parameters, see the sections Specifying an SQL Query and Query Parameters in the “Zen Tables” chapter of Using Zen Components.
General Rules for Processing Queries
The general rules for processing the results of the query for a <report> or <group> are as follows:
The sqlexpression Property
You can provide an SQL query for the <table> or <group> using a COS expression with sqlexpression. The following example first defines a Zen report property called Pattern:
Property Pattern As %String(ZENURL = "PATTERN") [ InitialExpression = "M%" ];
Then uses the property in a COS expression that constructs an SQL query that defines the resultset which populates the report with data.
XData ReportDefinition [ XMLNamespace = "http://www.intersystems.com/zen/report/definition" ]
{
<report xmlns="http://www.intersystems.com/zen/report/definition"
 sqlexpression='""SELECT ID,NAME,SSN,AGE,HOME_STATE 
   FROM SAMPLE.PERSON WHERE NAME LIKE &apos;""_%report.Pattern_""&apos;""' 
 orderby="ID" name="people">
 <group name='Persons'  breakOnField="ID">
  <group name='Person' >
   <attribute field='ID' name='ID'/>
   <attribute field='AGE' name="AGE"/>
   <attribute field='Name' name='Name'/>
   <attribute field='DOB' name='DOB'/>
   <attribute field='SSN' name='SSN'/>
   <attribute field='HOME_STATE' name='HOME_STATE'/>
  </group>
  <group name="MorePersons" 
   sqlexpression='""SELECT ID,NAME,SSN,AGE,HOME_STATE 
     FROM SAMPLE.PERSON WHERE NAME LIKE &apos;""_%report.Pattern_""&apos;""' 
   orderby="ID">
   <element field="Name" name="Name"/>
  </group>
 </group>
</report>
}

The orderby Attribute in ReportDefinition
The orderby attribute provides a comma-separated list of fields that specify how to order the resultset retrieved by the query for this <report> or <group>. It overrides any ORDER BY phrase that is already present in the query, and is useful when you want to change the ordering of the resultset returned by a stored procedure or class and you are not able to rewrite the query for your Zen report. Note that orderby applies sorting on the resultset after it has been retrieved from the table, so the names you use in the orderby string must reflect aliases applied by the SQL SELECT statement. For information on using orderby in the ReportDisplay section of a report, see The orderby Attribute in ReportDisplay in the chapter Displaying Zen Report Data.
Any fields listed in the orderby string must already be included in the SELECT phrase for the query for this <report> or <group>. Also, it is not possible to use orderby with a <report> or <group> that has no explicitly defined query.
The orderby value can specify sorting in ascending or descending order. To do this, add a \” (backslash) and the string ASC or DESC to the name of a field in the orderby list. The default sort is in ascending order. The ASC or DESC string is not case-sensitive. For example:
orderby="customer\desc"
The following example provides a literal value for orderby. In this example, the query is ordered by SalesRep and Customer, rather than by SalesRep and SaleDate as in the original query.
<report
  xmlns="http://www.intersystems.com/zen/report/definition"
  name="myReport"
  sql="SELECT ID,Customer,Num,SalesRep,SaleDate
    FROM ZENApp_Report.Invoice
    WHERE (Month(SaleDate) = ?) OR (? IS NULL)
    ORDER BY SalesRep,SaleDate"
    orderby="SalesRep,Customer" >
    <!-- Supply values to the ? query parameters here -->
    <parameter expression='..Month'/>
    <parameter expression='..Month'/>
  <!-- Other report contents appear here -->
</report>
If the first character in the orderby string is a ! (exclamation point) then Zen reports interprets the remainder of the string as an ObjectScript expression that provides the string. The following example references a Zen report class property SortOrder to provide a value for the orderby attribute. Because the current class for ObjectScript expressions evaluated in the ReportDefinition block is the report, you can use the double dot syntax to reference the report class property. Note that because the evaluation context is different in the ReportDisplay block, different syntax is required.
<report
xmlns="http://www.intersystems.com/zen/report/definition"
name="myReport"
sql="SELECT ID,Customer,Num,SalesRep,SaleDate
    FROM ZENApp_Report.Invoice
    WHERE (Month(SaleDate) = ?) OR (? IS NULL)
    ORDER BY SalesRep,SaleDate"
    orderby="!..SortOrder" >
      <!-- Other report elements here -->
</report>
One way to make the previous example work is to specify the orderby value dynamically when you invoke the report. To do this, apply the ZENURL data type parameter to the corresponding Zen report class property SortOrder, as follows. An InitialExpression value can be helpful, but is not required as long as you supply a value for this property when invoking the report.
Property SortOrder As %String(ZENURL="$SORTME")
                   [ InitialExpression="SalesRep,Customer"];
Once you define SortOrder as shown, you can change the orderby value by invoking the report with a URI like this one. The following sample URI contains a line break for typesetting purposes only; a correct URI is all on one line.
http://localhost:57772/csp/mine/my.ZENReport.cls
?$SORTME=Customer,SaleDate&$EMBEDXSL=1
To enable the orderby attribute, the Zen report class must have its SQLCACHE class parameter set to 1 (true). This is the default value for SQLCACHE.
The OnCreateResultSet Callback Method
If you use the OnCreateResultSet attribute to specify a server-side callback method, as in this example:
<report
    xmlns="http://www.intersystems.com/zen/report/definition"
    name="PersonReport" OnCreateResultSet="CreateRS" >
  <parameter value="B"/>
  <group name="Person">
    <attribute name="Name" field="Name"/>
    <attribute name="Age" field="Age"/>
    <attribute name="FavoriteColors" field="FavoriteColors"/>
  </group>
</report>
Then the method named by the OnCreateResultSet attribute must be defined within the page class with the specific signature shown in the following example:
ClassMethod CreateRS(ByRef pSC As %Status, ByRef tParams) As %ResultSet
{
  set statement=##class(%SQL.Statement).%New()
  if '$$$isObject(statement) set pSC=%objlasterror Quit ""
  set sql="SELECT Name,Age,FavoriteColors FROM Sample.Person WHERE Name %STARTSWITH ?"
  set pSC=statement.%Prepare(sql)
  if $$$ISERR(pSC) Quit ""
  set statement.%SelectMode=2
  set rs=statement.%Execute(tParams(1))
  quit rs
}
Where:
The required signature for the method identified by the OnCreateResultSet attribute is different for Zen reports than it is for <tablePane>.
For more examples using query parameters, see the section Query Parameters in the “Zen Tables” chapter of Using Zen Components.
Break On Field or Expression
A <group> can use the breakOnExpression and breakOnField attributes to organize the records in the resultset that it has received from its containing <report> or <group>. To “break on” an item means to “end this group when the value of this resultset field changes.”
The resultset in question is the one defined by the parent of the <group> and not the <group> itself. For example, suppose the containing group and contained group are defined as follows. Ellipses (...) in this example show omitted syntax items:
<group name="SalesByState" sql="SELECT STATE,...">

  <group name="SalesByCity"
         sql="SELECT CITY ... FROM ... WHERE STATE=?"
         breakOnField="STATE">
    <parameter field="STATE"/>
    ...
  </group>
  ...
</group> 
The contained group lists all the cities for a state, and then when the state changes it closes the group. breakOnField refers to its containing group, SalesByState, for the same reason that <parameter> refers to its containing group, SalesByCity. The contained item filters the resultset that the container item provides.
Attributes for Break On Field or Expression
<group> support the following attributes for grouping records from its parent resultset. If neither “break on” attribute is supplied for a <group>, no filtering occurs; the <group> processes every record in the resultset from its parent’s query.
Attribute Description
breakOnExpression
ObjectScript expression to apply to the value of the field specified by breakOnField. In the expression, %val represents the actual value of the breakOnField field in the resultset record that is currently being processed. For syntax details, see The %val Variable section.
A general knowledge of ObjectScript is helpful in knowing how to construct these expressions. In addition to the ObjectScript tips in The %val Variable section, see Using Caché ObjectScript, particularly the String Relational Operators section in the chapter Operators and Expressions.”
breakOnField
Name of a field in the resultset returned by the <report> or <group> that contains this <group>. That is, when looking for a breakOnField value to assign to the nested <group>, you must look one level up, to the parent, to find a field that you can use to organize a nested <group>.
If you set the breakOnField attribute for a <group>, the query in the containing <report> or <group> must ORDER BY the field that you identify as the breakOnField for the nested <group>. This is because field breaking examines the resultset sequentially, and creates a break whenever the specified field changes, so if the query is not ordered by the breakOnField, a break may occur unexpectedly in the output for the nested <group>.
The following example uses both of the attributes listed in the previous table.
Suppose you want to group by month, and you have a method in the Zen report class called GetMonth which accepts a date as an input argument and returns a value indicating the month. Then you could set breakOnField to the resultset field that contains the date, and use breakOnExpression to calculate the month that you want to use to group the records, like this:
<report xmlns="http://www.intersystems.com/zen/report/definition"
        name='myReport'
        sql="SELECT ID,Customer,Num,SalesRep,SaleDate
             FROM ZENApp_Report.Invoice
             ORDER BY SalesRep,SaleDate">
  <group name="month"
         breakOnField="SaleDate"
         breakOnExpression="..GetMonth(%val)">
     <!-- contents of the group here -->
  </group>
</report>
Choosing Values for Break On Field or Expression
This section provides incorrect and correct examples of <group> elements that use breakOnField.
In the following example, the <group> named salesRep3ab is incorrect, because its uses breakOnField value is SSN, and SSN is a field from the group’s own query. It should be a field from the query of the parent <group>:
<group name="salesRep3a" breakOnField="Name"
      sql="SELECT HOME_CITY from sample.person WHERE Name=? ORDER BY HOME_CITY">
      <parameter field="Name"/>
      <attribute expression="$G(%node(2))" name="num"/>
      <attribute field="Name" name="name"/>
      <element name="City" field="HOME_CITY"/>
   <group name="salesRep3ab" breakOnField="SSN"
          sql="SELECT SSN from sample.person WHERE HOME_CITY=?">
          <parameter field="HOME_CITY"/>
          <element field="SSN" name="SSN3ab"/>
   </group>
   <group name="salesRep3ac"
          sql="SELECT SSN from sample.person WHERE HOME_CITY=?">
          <parameter field="HOME_CITY"/>
          <element field="SSN" name="SSN3ac"/>
   </group>
</group> 
The following is the correct equivalent of the previous, incorrect example. The <group> named salesRep3ab is correct, because HOME_CITY is a field in the query for the containing group, salesRep3a:
<group name="salesRep3a" breakOnField="Name"
      sql="SELECT HOME_CITY from sample.person WHERE Name=? ORDER BY HOME_CITY">
      <parameter field="Name"/>
      <attribute expression="$G(%node(2))" name="num"/>
      <attribute field="Name" name="name"/>
      <element name="City" field="HOME_CITY"/>
   <group name="salesRep3ab" breakOnField="HOME_CITY"
          sql="SELECT SSN from sample.person WHERE HOME_CITY=?">
          <parameter field="HOME_CITY"/>
          <element field="SSN" name="SSN3ab"/>
   </group>
   <group name="salesRep3ac"
          sql="SELECT SSN from sample.person WHERE HOME_CITY=?">
          <parameter field="HOME_CITY"/>
          <element field="SSN" name="SSN3ac"/>
   </group>
</group> 
ObjectScript Expressions for the Break On Field
If the first character in the breakOnField string is a ! (exclamation point) then Zen interprets the remainder of the string as an ObjectScript expression that provides the string. The following example references a Zen report class property GroupBy to provide values for the breakOnField and orderby attributes:
<group name="Admissions" queryClass="Report.CurrentAdmissions" 
       queryName="FindAllAdmsInWard" orderby="!..GroupBy" > 
  <parameter expression="..Hospital"/> 
  <parameter expression="..Unit"/> 
  <parameter expression="..Ward"/> 
  <parameter expression="..Consultant"/> 
  <parameter expression="..GroupOption"/> 
  <parameter expression="..SortOption"/> 
  <parameter expression="..UserName"/> 
  <group name="GroupBy" breakOnField="!..GroupBy"> 
    <group name="Admission" > 
      <attribute field="AdmDate" name="AdmDate"/> 
      <attribute field="AdmTime" name="AdmTime"/> 
      <attribute field="PatNo" name="URN"/> 
      <attribute field="AdmNo" name="AdmNo"/> 
      <attribute field="PatName" name="Surname"/> 
      <attribute field="PatName2" name="GivenName"/> 
      <attribute field="Sex" name="Sex"/> 
      <attribute field="Age" name="Age"/> 
      <attribute field="BedCode" name="BedCode"/> 
      <attribute field="LocationCode" name="LocationCode"/> 
      <attribute field="DoctorDesc" name="DoctorDesc"/> 
      <attribute field="insdesc" name="insdesc"/> 
      <attribute field="CARETYPDesc" name="CARETYPDesc"/> 
    </group> 
  </group> 
</group> 
One way to make the previous example work is to specify the breakOnField and orderby values dynamically when you invoke the report. To do this, apply the ZENURL data type parameter to the corresponding Zen report class property GroupBy, as follows. An InitialExpression value can be helpful, but is not required as long as you supply a value for this property when invoking the report.
Property SortOrder As %String(ZENURL="$SORTME") 
         [ InitialExpression="LocationCode" ];
Once you define SortOrder as shown, you can change the breakOnField and orderby values by invoking the report with a URI like this one. The following sample URI contains a line break for typesetting purposes only; a correct URI is all on one line.
http://localhost:57772/csp/mine/my.ZENReport.cls
?$SORTME=Hospital&$EMBEDXSL=1
Nested Groups
A group is nested when it is inside a <report> or other <group> element. A group’s level refers to how deeply the group is nested from the top of the report definition. Zen reports supports any number of nesting levels.
There are two major techniques for setting up the queries for nested groups. A Zen report can:
Important:
When processing a group, any data that does not match the break condition is passed to any nested groups.
When you use nested groups in Zen reports, references to fields within queries are resolved according to the following set of rules:
The following are some examples of resolving references to fields within queries:
To correctly process the data results returned by nested groups, review the rules in the section Building the <report> or <group> Query.” Also see the next section, Sibling Groups.”
Sibling Groups
Groups are siblings when they are contained within the same <report> or parent <group>, at the same level.
Important:
Sibling groups work only if the Zen report class has its SQLCACHE class parameter set to 1 (true). This is the default setting. If you set SQLCACHE to 0 (false), Zen reports works as before, but throws an error if the report uses sibling groups or elements.
There are two major techniques for setting up queries for sibling groups:
The following example illustrates the first approach. A parent group named company contains two sibling groups, cname and crev. The parent group defines breakOnField=“Company”. The two sibling groups use the value of “Company” to look up information about the company for each employee in the resultset provided by the report.
<report xmlns="http://www.intersystems.com/zen/report/definition"
 name="SiblingGroupReport" 
 sql="SELECT TOP 10 Name,Company FROM Sample.Employee ORDER BY Company" >
 <group name="company" breakOnField="Company" >
 <attribute name="companyID" field="Company" />
 <!-- Both sibling groups process all employee records. -->
 <!-- First sibling group looks up company name for each employee. -->
  <group name="cname" 
   sql="SELECT Name FROM Sample.Company WHERE ID = ?">
   <parameter field="Company"/>
   <attribute name="employee" field="Name" />
   <element name="company_name" field="Name" />
  </group>
  <!-- Second sibling group looks up company revenue for each employee. -->
  <group name="crev"
   sql="SELECT Revenue FROM Sample.Company WHERE ID = ?">
   <parameter field="Company"/>
   <attribute name="employee" field="Name" />
   <element name="company_revenue" field="Revenue" />
  </group>
 </group>
</report>
The following figure shows the XML output of this report. The parent group creates an XML element called company for each company in the resultset. To save space, the XML for the first company is closed in this image, but you can see the results where companyID="2". The report creates elements that provide the company name and company revenue for each employee.
Report Output
The following example illustrates the second approach. The report named SiblingGroupReport contains two sibling groups, EmployeeByCompany and CompanyName. The first group defines the field “Company” as the breakOnField. It processes all the employee records. When the value of the breakOnField field changes, Zen reports closes the group and passes the last record in the group to subsequent sibling groups. In this example, the second group uses the value of “Company” in that record to look up the company name.
<report xmlns="http://www.intersystems.com/zen/report/definition"
 name="SiblingGroupReport" 
 sql="SELECT TOP 10 Name,Age,Company AS CompanyID,Home_City,Home_State,Home_Zip 
 FROM Sample.Employee ORDER BY Company" >
 <!-- First sibling group processes all employee records, 
      breaking on Company. -->
 <group name="EmployeeByCompany" breakOnField="CompanyID" >
  <attribute name="CompanyID" field="CompanyID" />
  <element name="name" field="Name"/>
  <element name="city" field="Home_City"/>
  <element name="state" field="Home_State"/>
  <element name="zip" field="Home_Zip"/>
 </group>
 <!-- Second sibling group gets only last employee record. 
      Uses it to look up company name.  -->
 <group name="CompanyName" 
  sql="SELECT Name FROM Sample.Company WHERE ID = ?" >
  <parameter field="CompanyID" />
  <attribute name="CompanyID" field="CompanyID" />
  <element name="name" field="Name"/>
 </group>
</report>
The following figure shows the output of this report. Again, the XML for companyID="1" is closed. You can see that for companyID="2", the report places output for all employees of that company in the element EmployeeByCompany. The element CompanyName contains the name of the company.
Report Output
Note:
As a convenience, Zen defines a special variable, %node(level) to be equal to the sequential number of the current sibling at the given grouping level, beginning at 1 for the first sibling. You can use this variable within an ObjectScript expression in an XData ReportDefinition block, for example:
<attribute expression="$G(%node(2))" name="num"/> 
To correctly process the data results returned by sibling groups, review the rules in the section Building the <report> or <group> Query.” Also see the previous section, Nested Groups,” and the discussion of sibling elements in the <element> topic.
Conditionally Generated Groups
The <group> element in the XData ReportDefinition block supports an ifexpression attribute that lets the user choose at runtime which ZEN Report groups are output. If the value of the attribute is 1, which is the default, the group is generated at runtime. Setting the attribute to 0 suppresses generation of that group and all of its subgroups. You can use a ZENURL property to control the value of the attribute and turn off generation of a group at runtime.
The following example uses the ZENURL property IncludeRecord, whose definition is shown here:
Property IncludeRecord As %Boolean(ZENURL="$INCLUDERECORD")
   [ InitialExpression = 0 ];
It controls whether the report generates the group named record:
<group name="record" ifexpression="..IncludeRecord">
  <attribute name='id' field='ID' />
  <attribute name='number' field='Num' />
  <element name='date' field='SaleDate' />
  <element name='customer' field='Customer' />
</group>
Value Nodes
This topic describes the elements that display the data contents of the report. Any of these elements may be a child of <report> or <group> in an XData ReportDefinition block. The elements are:
Handling White Space
By default, Zen reports strip out carriage return (ASCII 13) characters when processing the XML source data for a report. Stripping of carriage return characters is controlled by the attribute escape, which is available on <element>, <attribute>, and <aggregate> elements. escape can have the following values:
Special newline handling applies only to <element> elements, never to <attribute> elements. XML does not allow attribute values to contain newline characters.
Value Node Attributes
The value nodes <element>, <attribute>, and <aggregate> all have the following attributes.
Attribute Description
accumIf
(Optional) It can be convenient to conditionally accumulate aggregates for a Zen report. For this reason, value nodes have an accumIf attribute whose value is an ObjectScript expression that evaluates to 0 (false) or non–zero (true). If the accumIf expression for a value node evaluates to false, Zen skips that value node. As a consequence, the value node contributes nothing to the data source for the report. See the section accumIf following this table.
expression
(Optional) ObjectScript expression that processes the field value before outputting it. Within the expression you can use the %val variable to represent the actual value of the field. For syntax details, see The %val Variable section.
The following example would work in a Zen report class that defined a GetDisplayURL() method with one input argument:
The expression attribute can be used without %val to return static data, such as the report run time in the following example. This example uses the ObjectScript function $ZDATETIME ($ZDT) and the special value $HOROLOG ($H) to return a timestamp value:
A general knowledge of ObjectScript is helpful in knowing how to construct these expressions. In addition to the ObjectScript tips in The %val Variable section, see Using Caché ObjectScript, particularly the String Relational Operators section in the chapter Operators and Expressions.”
field
(Required) field specifies which resultset field supplies the data in the XML output. The referenced field must actually exist in the resultset for this node. This is the resultset from the closest query above this node among its ancestors in the XData ReportDefinition block, either:
  • The query for the <report> or <group> that contains the <element>
  • The query inherited by the <group> that contains the <element>. This happens when the <group> that contains the <element> does not provide a query of its own and instead inherits its query from the nearest ancestor <report> or <group>. In this case, the <element> may reference any field in the inherited resultset, just as if the query were defined at the same level as the <group> that contains the <element>.
In an expression or filter for the value node, you can use the %val variable to represent the actual value of the field.
If the first character in the field string is a ! (exclamation point) then Zen interprets the remainder of the string as an ObjectScript expression that provides the string. See the section Field following this table.
fields
fields is similar to field, but consists of a comma-separated list of one or more fields from the resultset. In an expression or filter for the value node, you can refer to these fields using the %val variable subscripted with their case sensitive names as listed in fields.
For %val syntax details, see The %val Variable.”
filter Not all value nodes support the filter attribute. <element> and <attribute> support filter; <aggregate> does not. For information about filter, see the individual value node descriptions.
name
Generates an XML element of this name in the output. Suppose there is a field called month in the resultset for the <report> or <group> that contains this <element>, and suppose one of the valid values for month is the string July. An entry like this:
<element name="myMonth" field="month" />
Could yield an element like this in the XML that defines the data for the report:
<myMonth>July</myMonth>
If the supplied name is an invalid string for use as an XML identifier, an error results when you attempt to compile the Zen report class. The most obvious characters to avoid are any white space characters, plus the five standard XML entity characters &'<>"
If no name is supplied, Zen uses the name item
accumIf
The syntax for the accumIf expression is intended to be ObjectScript. However, issues with XML escaping in XData ReportDefinition syntax require you to use quoting conventions carefully in the accumIf value. Enclose the value in single quotes and double any double quotes that you would normally use in ObjectScript syntax. The following example shows this convention applied to the double quotes around Region and Northeast in the <aggregate> accumIf value:
<report xmlns="http://www.intersystems.com/zen/report/definition"
        name="InsurancePolicies" 
        sql="SELECT Region,Flood,InsuredValue 
             FROM InsurancePolicies ORDER BY Flood"> 
  <group name="Flood" breakOnField="Flood" > 
    <attribute name="Flood" field="Flood"/> 
    <group name="Policy"> 
      <attribute name="Region" field="Region"/> 
      <attribute name="InsuredValue" field="InsuredValue"/> 
    </group> 
    <aggregate name="NortheastTotal" field="InsuredValue" 
               fields="Region" type="SUM"
               accumIf='%val(""Region"")=""Northeast""' /> 
  </group> 
</report>
In this example, there are two possible values for Flood: "Y" and "N" indicates whether or not the property is in a federally designated flood zone. For each of these values, the <aggregate> calculates the value of insured policies in the Northeast region.
field
If the first character in the field string is a ! (exclamation point) then Zen interprets the remainder of the string as an ObjectScript expression that provides the string. The following example references a Zen report class property GroupBy to provide a value for the field attribute of <attribute> as well as the breakOnField and orderby attributes of other elements:
<group name="ReportTime"> 
 <attribute name="timestamp" expression="$ZDATETIME($H, 2, 2)"/> 
</group> 

<group name="Admissions" queryClass="Report.CurrentAdmissions" 
 queryName="FindAllAdmsInWard" orderby="!..GroupBy" > 
 <parameter expression="..Hospital"/> 
 <parameter expression="..Unit"/> 
 <parameter expression="..Ward"/> 
 <parameter expression="..Consultant"/> 
 <parameter expression="..GroupOption"/> 
 <parameter expression="..SortOption"/> 
 <parameter expression="..UserName"/> 
 <group name="GroupBy" breakOnField="!..GroupBy"> 
  <group name="Admission" > 
   <attribute field="!..GroupBy" name="groupby"/> 
   <attribute field="AdmDate" name="AdmDate"/> 
  </group>
 </group>
</group>
One way to make the previous example work is to specify the field, breakOnField, and orderby values dynamically when you invoke the report. To do this, apply the ZENURL data type parameter to the corresponding Zen report class property GroupBy, as follows. An InitialExpression value can be helpful, but is not required as long as you supply a value for this property when invoking the report.
Property GroupBy As %String(ZENURL="$SORTME") 
         [ InitialExpression="LocationCode" ];
Once you define GroupBy as shown, you can change the field, breakOnField, and orderby values by invoking the report with a URI like this one. The following sample URI contains a line break for typesetting purposes only; a correct URI is all on one line.
http://localhost:57772/csp/mine/my.ZENReport.cls
?$SORTME=Hospital&$EMBEDXSL=1
<element>
The <element> element is valid within a <report> or <group> in an XData ReportDefinition block. Each <element> adds an XML element to the XML data definition for the report.
<element> Attributes
<element> has the following attributes.
Attribute Description
Value node attributes For descriptions, see the section Value Node Attributes.”
escape
Browsers generally remove what they regard as excess white space from pages that they display. Therefore, if you want to retain white space characters in the output you must use the escape attribute. escape has the following possible values:
  • "xml" — (Default) The text is XML escaped. This means that spaces are visible in the XML source, but do not appear in the display unless you set the literalSpaces attribute for the corresponding <item>. Zen reports strip out carriage return (ASCII 13) characters when processing the XML source data for a report.
  • "none" — All characters are preserved regardless of whether or not the original text contains spaces or newline characters. No XML escaping takes place, and all characters are enclosed in CDATA syntax. Zen reports do not strip out carriage return (ASCII 13) characters.
  • "noneifspace" — Any text that contains line feed or space characters is enclosed in CDATA syntax. Zen reports strip out carriage return (ASCII 13) characters when processing the XML source data for a report.
  • "passthru" — No XML escaping is done. To keep the XML document valid, the XML data inside the element must be valid. For example, every opening element tag such as <foo> must be matched by a closing element tag </foo>. Zen reports do not strip out carriage return (ASCII 13) characters.
fieldType
A string that indicates the type of data retrieved by the element. The value of the fieldType attribute is either "literal" (which is the default) or "stream". When the fieldType is "stream", the element field must retrieve a stream, or run-time errors result. When the fieldType is "stream" you cannot use the expression attribute or %val. If you want to process the OID using %val let fieldType be "literal", which is the default. The <element> escape attribute can be used to determine how the stream is translated, for instance whether < is transformed to less than entity.
filter
ObjectScript expression that may or may not evaluate to 0 (false). When the filter expression evaluates to 0, Zen skips processing this <element>. As a result, no output from this <element> appears in the XML data for the report.
In the filter expression, you can refer to the values of fields from the resultset query using the %val variable. You can use:
  • Single-valued %val to represent the value of the field identified by field attribute for this <element>
  • %val subscripted with the case sensitive names listed in the fields attribute for this <element>
For details, see The %val Variable section.
A general knowledge of ObjectScript is helpful in knowing how to construct these expressions. In addition to the ObjectScript tips in The %val Variable section, see Using Caché ObjectScript, particularly the String Relational Operators section in the chapter Operators and Expressions.”
excelName
If you are generating an Excel spreadsheet from a Zen report, you can use the excelName attribute to provide a string to use as the header for the column generated from this <element>. If excelName is null, the column header comes from the name attribute. The excelName attribute supports localization. See Localizing Zen Reports.
excelNumberFormat
If you are generating an Excel spreadsheet from a Zen report, you can use the excelNumberFormat attribute to provide a string that tells Excel how to format the number. This attribute is used only when generating an Excel spreadsheet in xlsx mode, and when EXCELMODE is set to “element”. See Numbers, Dates and Aggregates.
isExcelDate
By default, the value supplied by an <element> is interpreted as text in the generated Excel spreadsheet. If you set the attribute isExcelDate="true", the value is interpreted as a date in Excel. The date must be in Excel date format, which is the date display format for fetching via SQL when %DATE or a %TIMESTAMP are present and runtimeMode="1" (ODBC mode). If Excel cannot interpret the value as a date, you see an error when Excel tries to open the generated spreadsheet. See Numbers, Dates and Aggregates.
This attribute has the underlying data type %ZEN.Datatype.boolean. See Zen Reports Attribute Data Types.”
isExcelNumber
By default, the value supplied by an <element> is interpreted as text in the generated Excel spreadsheet. If you set the attribute isExcelNumber="true", the value is interpreted as a number in Excel. If Excel cannot interpret the value as a number, you see an error when Excel tries to open the generated spreadsheet. See Numbers, Dates and Aggregates.
This attribute has the underlying data type %ZEN.Datatype.boolean. See Zen Reports Attribute Data Types.”
isExcelTime
By default, the value supplied by an <element> is interpreted as text in the generated Excel spreadsheet. If you set the attribute isExcelTime="true", the value is interpreted as a time in Excel. The time must be in Excel time format, which is the time display format for fetching via SQL when %DATE or a %TIMESTAMP are present and runtimeMode="1" (ODBC mode). If Excel cannot interpret the value as a time, you see an error when Excel tries to open the generated spreadsheet. See Numbers, Dates and Aggregates.
This attribute has the underlying data type %ZEN.Datatype.boolean. See Zen Reports Attribute Data Types.”
Elements as Siblings of Groups
A <group> may be a sibling of another <group>. There is an extended discussion of Sibling Groups in the section about the <group> element. An <element> may also be a sibling of one or more <group> elements at the same level of the XData ReportDefinition block. When this is the case, the <group> that is sequentially the first sibling group at that level has special significance for each <element> and <group> at that level. This <group> is called the collegial group for that level.
Note:
Elements work as siblings of groups only if the Zen report class has its SQLCACHE class parameter set to 1 (true). This is the default setting. If you set SQLCACHE to 0 (false), Zen reports works as before, but throws an error if the report uses sibling groups or elements.
Suppose an <element> and a <group> are siblings. Any <group> at this level gets its data either from its own query, or from the query defined by its next nearest ancestor <report> or <group>. There is much more detail about this in the Building the <report> or <group> Query and Break On Field or Expression sections of the <group> documentation in this chapter.
Meanwhile, any <element> at the sibling level gets its data from the field whose containing row in the resultset satisfies the break condition of the collegial group. If an <element> has no collegial group, its contents in the XML output consist of the value of the identified field for each row in the resultset, even if these are not distinct values.
<attribute>
The <attribute> element is valid within a <report> or <group> in an XData ReportDefinition block. Each <attribute> adds an attribute to the XML data definition for the report. This attribute modifies the element output by the <report> or <group> that contains the <attribute>.
The syntax restrictions on attributes and elements can be difficult to understand initially. A beginning user often tries the following in a Zen report XData ReportDefinition block:
<group name="surprise">
  <element name="this" field="contains_the_value_x" />
  <element name="that" field="contains_the_value_w" />
  <attribute name="other" field="contains_the_value_y" />
  <attribute name="thing" field="contains_the_value_z" />
</group>
Expecting this XML output:
<surprise>
  <this>x</this>
  <that other="y" thing="z">w</that>
</surprise>
Whereas the XML output is actually:
<surprise other="y" thing="z">
  <this>x</this>
  <that>w</that>
</surprise>
It is not possible to nest an <attribute> inside an <element> in a Zen report XData ReportDefinition block. So you cannot do this:
<element name="that" field="contains_the_value_w" >
  <attribute name="other" field="contains_the_value_y" />
  <attribute name="thing" field="contains_the_value_z" />
</element>
In an attempt to create this output XML:
<that other="y" thing="z">w</that>
The <that> node shown in the previous example uses valid XML syntax, but cannot be generated from any combination of <element>, <attribute>, and <group> elements in a Zen report XData ReportDefinition block. In the XML generated by Zen reports:
Suppose you wanted to create nested elements and attributes in your XML output, along these lines:
<surprise>
  <this>x</this>
  <that other="y" thing="z">w</that>
</surprise>
The closest a Zen report XData ReportDefinition block could come to generating the previous example would be the following:
<group name="surprise">
 <element name="this" field="contains_the_value_x" />
 <group name="that">
  <element name="there" field="contains_the_value_w" />
  <attribute name="other" field="contains_the_value_y" />
  <attribute name="thing" field="contains_the_value_z" />
 </group> </group>
Which would generate the following output XML:
<surprise>
  <this>x</this>
  <that other="y" thing="z">
    <there>w</there>
  </that>
</surprise>
<attribute> has the following attributes.
Attribute Description
Value node attributes For descriptions, see the section Value Node Attributes.”
escape
Browsers generally remove what they regard as excess white space from pages that they display. Therefore, if you want to retain white space characters in the output you must use the escape attribute. escape has the following possible values:
  • "xml" — (Default) The text is XML escaped. This means that spaces are visible in the XML source, but do not appear in the display unless you set the literalSpaces attribute for the corresponding <item>. Zen reports strip out carriage return (ASCII 13) characters when processing the XML source data for a report.
  • "none" — All characters are preserved regardless of whether or not the original text contains spaces or newline characters. No XML escaping takes place, and all characters are enclosed in CDATA syntax. Zen reports do not strip out carriage return (ASCII 13) characters.
  • "noneifspace" — Any text that contains line feed or space characters is enclosed in CDATA syntax. Zen reports strip out carriage return (ASCII 13) characters when processing the XML source data for a report.
filter
ObjectScript expression that may or may not evaluate to 0 (false). When the filter expression evaluates to 0, Zen skips processing this <attribute>. As a result, no output from this <attribute> appears in the XML data for the report.
In the filter expression, you can refer to the values of fields from the resultset query using the %val variable. You can use:
  • Single-valued %val to represent the value of the field identified by field attribute for this <attribute>
  • %val subscripted with the case sensitive names listed in the fields attribute for this <attribute>
For details, see The %val Variable section.
A general knowledge of ObjectScript is helpful in knowing how to construct these expressions. In addition to the ObjectScript tips in The %val Variable section, see Using Caché ObjectScript, particularly the String Relational Operators section in the chapter Operators and Expressions.”
<aggregate>
The <aggregate> element performs a calculation over every record in the resultset returned by the query associated with a <report> or <group>. The result becomes the contents of a node in the XML data for the report.
<aggregate> has the following attributes.
Attribute Description
Value node attributes For descriptions, see the section Value Node Attributes.”
class
If the type is CUSTOM, the class attribute must specify the package and class name of a class that extends %ZEN.Report.CustomAggregate.
There are several built-in aggregate classes that you can reference. For details, see the list of Built-in Aggregate Classes following this table.
For custom functionality, create your own %ZEN.Report.CustomAggregate subclass. See Creating a New Aggregate Class.”
escape
Browsers generally remove what they regard as excess white space from pages that they display. Therefore, if you want to retain white space characters in the output you must use the escape attribute. escape has the following possible values:
  • "xml" — (Default) The text is XML escaped. This means that spaces are visible in the XML source, but do not appear in the display unless you set the literalSpaces attribute for the corresponding <item>. Zen reports strip out carriage return (ASCII 13) characters when processing the XML source data for a report.
  • "none" — All characters are preserved regardless of whether or not the original text contains spaces or newline characters. No XML escaping takes place, and all characters are enclosed in CDATA syntax. Zen reports do not strip out carriage return (ASCII 13) characters.
  • "noneifspace" — Any text that contains line feed or space characters is enclosed in CDATA syntax. Zen reports strip out carriage return (ASCII 13) characters when processing the XML source data for a report.
excelFormula
Specifies that this aggregate should be an Excel formula in the generated spreadsheet. The value must be the name of the Excel formula equivalent to the type of the aggregate. See Numbers, Dates and Aggregates.
excelName
When you use a Zen report to generate an Excel spreadsheet, aggregates are often positioned at the bottom of a column generated by elements in the ReportDefinition. In this case, the aggregate uses the column header generated by the element. You can generate an Excel spreadsheet from a report that has aggregates, but no elements. In this case, the default value for the column header comes from the name attribute. You can also use the excelName attribute to provide a string to use as the column header for this <aggregate>. See Numbers, Dates and Aggregates.
The excelName attribute supports localization. See Localizing Zen Reports.
excelNumberFormat
If you are generating an Excel spreadsheet from a Zen report, you can use the excelNumberFormat attribute to provide a string that tells Excel how to format the number. This attribute is used only when generating an Excel spreadsheet in xlsx mode, and when EXCELMODE is set to “element”. See Numbers, Dates and Aggregates.
filter
Supports conditional inclusion of the aggregate. When the filter evaluates to 1 the report includes the aggregate in the output. When the filter evaluates to 0 the report does not include aggregate. The filter expression can use the special variable %val, which contains the value of the aggregate.
format
ObjectScript expression that formats the output from this aggregate. The format expression can use the special variable %val, which contains the value of the aggregate.
ignoreNLS
Used only when the runtime mode is DISPLAY (2). If set to 1, do not perform any National Language Settings processing for this aggregate. If set to 0, perform NSL processing. If null (""), ignoreNLS takes its value from the value of the parameter AGGREGATESIGNORENLS . The default value is "". See National Language Settings for Aggregates.
postprocessResult
Used only when the runtime mode is DISPLAY (2). If set to 1, perform postprocessing of the aggregate result for National Language Settings. If false, do not perform postprocessing. If null (""), the value of this attribute is set to 1 during report generation. The default value is "". See National Language Settings for Aggregates.
preprocessValue
Used only when the runtime mode is DISPLAY (2). If set to 1, perform preprocessing of the aggregate value for National Language Settings. If false, do not perform preprocessing. If null (""), the value of this attribute is set to 1 during report generation. The default value is "". See National Language Settings for Aggregates.
type
Specifies which kind of aggregation to perform. Valid values include the following case-sensitive strings:
  • "AVG" — the average of all values
  • "CUSTOM" — (see the class attribute)
  • "COUNT" — the total number of records
  • "MAX" — the maximum value in the set
  • "MIN" — the minimum value in the set
  • "SUM" — the sum of all values
Formatting an Aggregate
The format attribute lets you apply formatting to the output of an aggregate. The following example is derived from the ZENApp.MyReport class in the SAMPLES database. It formats the aggregate named avg, using the ObjectScript function $NUMBER:
<group name='SalesRep' breakOnField='SalesRep'>
 <attribute name='name' field='SalesRep' />
 <aggregate name='count' type="COUNT" field='Num' />
 <aggregate name='subtotal' type="SUM" field='Num' />
 <aggregate name='avg' type="AVG" field='Num' format="$number(%val,2)" />
 <group name="record">
  <attribute name='id' field='ID' />
  <attribute name='number' field='Num' />
  <element name='date' field='SaleDate' />
  <element name='customer' field='Customer' />
 </group>
</group>
The following XML fragment shows the three aggregates. Note the formatting of avg:
XML Output
A general knowledge of ObjectScript is helpful in knowing how to construct these expressions. In addition to the ObjectScript tips in The %val Variable section, see Using Caché ObjectScript, particularly the String Relational Operators section in the chapter Operators and Expressions.”
National Language Settings for Aggregates
Zen reports supports National Language Settings (NLS) when calculating aggregates. NLS support is controlled by the parameter AGGREGATESIGNORENLS, and the properties ignoreNLS, preprocessValue, and postprocessResult. The default value of AGGREGATESIGNORENLS is true (1), and the default value of all the properties is null (""). In the default case, the ignoreNLS property for each aggregate takes its value from AGGREGATESIGNORENLS. It is set to 1, and the report ignores National Language Settings. To apply NLS to aggregates in a report, set AGGREGATESIGNORENLS to false (0). You can also control NLS processing on a per-aggregate basis by setting the value of ignoreNLS, which overrides the value of AGGREGATESIGNORENLS.
Note:
Numeric XSLT functions only honor the US locale. For this reason, an aggregate does not display properly if it uses XSLT functions that have numeric floating point arguments, and National Language Settings that use commas to separate decimal values.
When you create a report with the runtime mode set to DISPLAY (2), and Caché is using NLS, the input values used to calculate the aggregate must be converted from their NLS format to a format that can be used by the COS code that does the calculation. Once the aggregate has been calculated, it must be converted from the format produced by COS to a format consistent with the National Language Settings in use.
Support for NLS is implemented by methods in the base aggregate class %ZEN.Report.aggregate. If AGGREGATESIGNORENLS is false (0), these methods pre-process values or post-process results according to the National Language Settings in use. You can extend the base class and define whatever pre- and post-processing methods you need.
If the report is using NLS, and the value of preprocessValue, or postprocessResult is null (""), it is changed to 1 during report generation. You can override this behavior by explicitly setting the value of either preprocessValue, or postprocessResult to 1 or 0. If the report is ignoring NLS, the value of these properties is ignored, and the report never calls the methods that perform NLS processing.
All of the standard and shipped custom aggregates provided by Zen reports work transparently with NLS when AGGREGATESIGNORENLS is false (0). If you need to define a custom aggregate, and the custom aggregate needs to work with National Language Settings, use the aggregates defined in Zen reports as a template.
Built-in Aggregate Classes
The following is a list of built-in custom aggregate classes that you can specify using the <aggregate> class attribute when the <aggregate> type is CUSTOM. The alternative to using these built-in classes is to create your own class, as described in the section Creating a New Aggregate Class.”
%ZEN.Report.Aggregate.Correlation
Returns the correlation coefficient between two sets of values. Returns an empty string if the denominator would be zero.
This aggregate accepts a %List of two sets of values as an argument. You can supply this argument using the $LISTBUILD function and the %val variable with the <aggregate> expression attribute. For example:
<report xmlns="http://www.intersystems.com/zen/report/definition" 
        name="SampleCorrelation" 
        sql="SELECT AmountOfChocolateConsumed,AgeAtDeath 
             FROM Correlation.TestData"> 
  <aggregate type="CUSTOM" class="%ZEN.Report.Aggregate.Correlation" 
     name='DeathByChocolate' 
     expression='$LB(%val("AmountOfChocolateConsumed"),%val("AgeAtDeath"))' 
     fields='AmountOfChocolateConsumed,AgeAtDeath' /> 
</report>
%ZEN.Report.Aggregate.CountDistinct
Returns the number of distinct values in a set of data, as opposed to a simple COUNT.
%ZEN.Report.Aggregate.Covariance
Returns the statistical covariance of the values processed. This is a measure of the degree to which two variables change together.
This aggregate accepts a %List of two sets of values as an argument. You can supply this argument using the $LISTBUILD function and the %val variable with the <aggregate> expression attribute. For example:
<report xmlns="http://www.intersystems.com/zen/report/definition" 
        name="relationship" sql="SELECT x,y FROM Test.XYData">  
  <aggregate type="CUSTOM" class="%ZEN.Report.Aggregate.Covariance" 
     name='covariance' expression='$LB(%val("x"),%val("y"))' fields='x,y' />
</report>
%ZEN.Report.Aggregate.LinearRegression
This custom aggregate class is not meant for displaying a value in Zen reports but is used internally by other linear regression aggregates to calculate their aggregate values. The return value is a %List with two elements that provide the coefficients a and b of the linear equation that best approximates a graph of the relationship between the two sets of input values x and y:
y = (a * x) + b
The input argument is a %List of two sets of values that provide x and y in the linear equation. You can supply this argument using the $LISTBUILD function and the %val variable with the <aggregate> expression attribute. For example:
<report xmlns="http://www.intersystems.com/zen/report/definition" 
 name="xydata" sql="SELECT x,y FROM Test.XYData"> 
  <aggregate type="CUSTOM" class="%ZEN.Report.Aggregate.LinearRegression" 
     name='linreg' expression='$LB(%val("x"),%val("y"))' fields='x,y' />
</report>
%ZEN.Report.Aggregate.LinRegIntercept
See the description of %ZEN.Report.Aggregate.LinearRegression.
This aggregate accepts a %List of two sets of values x and y, and returns the coefficient b (the y-intercept value) of the linear equation that best approximates a graph of the relationship between the values x and y:
y = (a * x) + b
%ZEN.Report.Aggregate.LinRegR2
Returns the coefficient of determination, which provides a measure of how well future outcomes are likely to be predicted by the statistical model.
%ZEN.Report.Aggregate.LinRegSlope
See the description of %ZEN.Report.Aggregate.LinearRegression.
This aggregate accepts a %List of two sets of values x and y, and returns the coefficient a (the slope value) of the linear equation that best approximates a graph of the relationship between the values x and y:
y = (a * x) + b
%ZEN.Report.Aggregate.LinRegVariance
Returns the statistical variance of the values processed for linear regression.
%ZEN.Report.Aggregate.Median
Returns the median of a set of numerical data, as opposed to a simple AVG (average). The median is a number with half of the data set of greater value than it, and half of lesser value.
For a data set with an odd size, the median is a member of the data set. For a data set with an even size, the median is a value halfway between two members of the data set.
%ZEN.Report.Aggregate.Mode
Returns the statistical mode (most frequent observation) of a set of data.
%ZEN.Report.Aggregate.StDev
Returns the standard deviation of the values processed. This is the unbiased standard deviation using Bessel’s correction of n - 1 in the denominator rather than n.
%ZEN.Report.Aggregate.StDevP
Returns the biased standard deviation of a whole population of values provided.
%ZEN.Report.Aggregate.Var
Returns the statistical variance of the values processed. This is square of the unbiased standard deviation.
%ZEN.Report.Aggregate.VarP
Returns the biased statistical variance of a whole population of values provided.
Creating a New Aggregate Class
If you need a new type of aggregate, you can develop and use a custom aggregate class as follows:
  1. Provide a name for the aggregate:
    The name myaggregatename must be unique in the XML namespace.
  2. If you need to provide parameters to the aggregate, define them as properties of the class.
  3. Override the following methods:
    You may use the single-valued or multidimensional special variable %val inside these methods.
  4. Save and compile the class as myPackage.myClassName
  5. You can reference this aggregate in a report, using myaggregatename as the element name. You can pass parameters to the aggregate as attributes. You do not need to provide the type or class attributes.
The following example creates two custom aggregate classes, called me.MultiDimAggregate andme.ParameterizedAggregate, and uses them in a report.
Class me.MultiDimAggregate Extends %ZEN.Report.CustomAggregate
{
 Parameter XMLNAME = "multidim";
 Property Count As %Integer [ InitialExpression = 0 ];
 /// Processes each new value
 Method ProcessValue(ByRef pValue As %String) As %Status
  {
   if $e(pValue("Name"))="A" Set ..Count=..Count+1
   if $e(pValue("Home_State"))="A" Set ..Count=..Count+1
  }
 /// Return the count of names and states that begin with "A"
 Method GetResult() As %String
  {
   quit ..Count
  }
}
The class me.ParameterizedAggregate uses the %ZEN.Report.Datatype.string datatype for the FieldToCount property. The value of a property having this datatype must be the name of a field in the result set. This datatype supports the parameter REPORTFIELD, which triggers special processing, feeding the value of FieldToCount into a subscript of the %val special variable. The value of the field specified by FieldToCount is passed to %val as data at that subscript.
For other properties, use datatypes from %ZEN.Datatype package or other datatypes as desired.
Class me.ParameterizedAggregate Extends %ZEN.Report.CustomAggregate
{
 Parameter XMLNAME = "mycountdistinct";
 Property exclude As %ZEN.Datatype.string;
 /// Array of values processed
 Property Values As array Of %String;
 /// Running count of distinct values processed
 Property Count As %Integer [ InitialExpression = 0 ];
 /// The field you're counting
 Property FieldToCount As %ZEN.Report.Datatype.string;
 /// Processes each new value
 Method ProcessValue(ByRef pValue As %String) As %Status
 {
   if pValue(..FieldToCount)="" quit $$$OK
   if $e(pValue(..FieldToCount))=..exclude quit $$$OK
   If ..Values.GetAt(pValue(..FieldToCount)) {
            #; seen it already
    } Else {
      Do ..Values.SetAt(1,pValue(..FieldToCount))
      Set ..Count=..Count+1
    }
   Quit $$$OK
 }

 /// Return the count of distinct values processsed
 Method GetResult() As %String
 {
   Quit ..Count
 }
}
<report xmlns="http://www.intersystems.com/zen/report/definition"
     name="MyReport" sql="select top 20 Name,Home_Street,
     Home_City,Home_State From Sample.Person order by Home_State">
  <group name="State" breakOnField="Home_State">
    <group name="Person">
      <attribute name="Name" field="Name"/>
      <attribute name="Street" field="Home_Street"/>
      <attribute name="City" field="Home_City"/>
      <attribute name="State" field="Home_State"/>
    </group>
  <aggregate name="countSomething" field="!..Field" type="CUSTOM" 
     class="%ZEN.Report.Aggregate.CountDistinct" />
  <mycountdistinct name="mycount" exclude='#($zcvt("a","u"))#' FieldToCount="Name"/>
  <multidim name="mymultidim" fields="Name,Home_State" expression="%val"/>
  </group>
</report>
This example report uses three aggregates. The first is a built-in custom aggregate class, see Built-in Aggregate Classes:
<aggregate name="countSomething" field="!..Field" type="CUSTOM" 
     class="%ZEN.Report.Aggregate.CountDistinct" />
The second is an aggregate of the custom type defined in me.ParameterizedAggregate. Note that the value passed in the exclude attribute is a Zen expression.
<mycountdistinct name="mycount" exclude='#($zcvt("a","u"))#' FieldToCount="Name"/>
The third is an aggregate of the custom type defined in me.MultiDimAggregate. Note that the value of the expression attribute is %val . In this case, %val is passed to ProcessValue by reference. In other words, the aggregate passes .%val. This gives the ProcessValue method access to the multidimensional array called %val.
<multidim name="mymultidim" fields="Name,Home_State" expression="%val"/>
DATASOURCE
Identifies an XML document that contains the data for the Zen report. The DATASOURCE value can be any of the following:
When a Zen report identifies a DATASOURCE, it ignores EMBEDXSL or $EMBEDXSL. You cannot use embedded XSLT with a data source. The report property suppressRootTag can be useful with DATASOURCE if the data source includes its own root tag.
A user can override the current DATASOURCE setting for the report class by providing a $DATASOURCE parameter in the URI when invoking the Zen report from a browser.
If you are invoking the report from the command line using the GenerateReport method, Zen reports looks for the data source files in the same location as it does when the report is run in a browser.
If you want to override the class’s defined DATASOURCE value during this command sequence, you can set the report’s Datasource property to the desired value, as in the following command line example:
 zn "SAMPLES"
 set %request=##class(%CSP.Request).%New()
 set %request.URL = "/csp/samples/datacurrent.xml"
 set %request.CgiEnvs("SERVER_NAME")="127.0.0.1"
 set %request.CgiEnvs("SERVER_PORT")=57777
 set rpt=##class(jsl.TimeLine).%New()
 set rpt.Datasource = "/csp/samples/datanew.xml"
 set tSC=rpt.GenerateReport("C:\TEMP\timeline.pdf",2)
 if 'tSC do $system.Status.DecomposeStatus(tSC,.Err) write !,Err(Err) ;'
 write !,tSC
Including an XML Data Source
This section describes techniques that allow you to provide XML statements that contribute directly to the XML data source for your Zen report. Topics in this section include:
Writing XML Statements From a Class Method
You can write XML statements from a class method in the language of your choice, then reference this method from the XData ReportDefinition block in the Zen report class using the call and callClass attributes on the top level <report> element. In this case, there is no need for your XData ReportDefinition block to provide statements that generate an XML data source. Instead, your XData ReportDefinition block consists of a single <report> element that provides a call and (optionally) a callClass attribute.
The class method that writes out an XML data source can be in the same Zen report class as XData ReportDefinition, or it can be in some other class. The following example is an ObjectScript method, but you could use MultiValue or Basic as the language for this method:
ClassMethod CreateXML() { 
    WRITE !,"<MyExample>"
    WRITE !,"some text for a text node" 
    WRITE !,"</MyExample>"
 }
To use this method, the XData ReportDefinition block looks like the following example. If the method identified by the call attribute is in the same class, the <report> element in needs to specify only the call attribute. Zen looks for a method of this name in the same class:
XData ReportDefinition 
      [XMLNamespace="http://www.intersystems.com/zen/report/definition"]
{ 
<report xmlns="http://www.intersystems.com/zen/report/definition"
        name="myReport" call="CreateXML"> 
</report> 
} 
If the method is in some other class in the same Caché namespace, Zen can find it if you supply a callClass attribute in addition to call. The value of the callClass attribute must be the full package and class name of the class that contains the call method. If not supplied, the default is the class in which the <report> or <group> appears. The callArgument attribute supplies an argument to the method specified by the call attribute.
Both <report> and <group> support the call, callClass, and callArgument attributes. If you specify the call attribute on the <report> element, the XML statements written by the class method comprise the complete source data for the report. If you specify the call attribute on the <group> element, the class method provides a portion of the source data for the report, positioned where the <group> element is located in the <report>.
<call>
The <call> element calls a method that returns a stream, and inserts the stream into the report definition at the place where the call element occurs. The stream must be well-formed XML. This capability allows a report definition to incorporate XML created by the XData ReportDefinition block of another report. It is useful if you want to create a report from separately developed subreports, or if a report becomes too large to compile. The <call> element must be a direct child of <report>. A called subreport cannot contain a <call> element.
The <call> element has the following attributes when used in the XData ReportDefinition block:
Attribute Description
hasStatus
If true, the method returns a status value by reference in last parameter to method.
method
A class or instance method which returns a stream. This method must be defined in the Zen report. The stream is inserted into the report definition at the place where the call element occurs.
The method can return the output of the XData ReportDefinition block of a subreport, or it can perform other functions. If used with a subreport, the method must create a new instance of the subreport, and use GenerateStream to return a stream. For methods called from the XData ReportDefinition block, the mode argument to GenerateStream is always 0 (XML), which is the default.
Important:
A different <call> element is used in the XData ReportDisplay block.
For help resolving problems with the <call> element, see Troubleshooting the <call> element.
Example using the <call> element
The SAMPLES namespace provides a code example in the ZENApp package. The Zen report class ZENApp.MyReportMainDef.cls defines a report that uses the <call> element in the XData ReportDefinition block:
<report xmlns="http://www.intersystems.com/zen/report/definition" 
  name='myReport' runonce="true"> 
 <call method="GetSub"/>  
</report>
The method GetSub creates a new instance of MyReport.cls and generates a stream containing the output of the XData ReportDefinition block. This stream is placed in the XML generated by MyReportMainDef.cls, at the point where the <call> element is placed. The second argument to GenerateStream supplies the mode, which is always 0, indicating XML, when you call the method from ReportDefinition.
Method GetSub() As %GlobalCharacterStream
{
 set stream=""
 set rpt=##class(ZENApp.MyReport).%New()
 i $isobject(rpt)
 {
   set tSC=rpt.GenerateStream(.stream,0)
 }
 quit stream
}
The section XData ReportDefinition in the chapter “Gathering Zen Report Data” discusses the structure of the XML generated by the XData ReportDefinition block. As this figure shows, the top-level element in the generated XML comes from the name attribute of the report element. When the ReportDefinition block uses <call> to add XML from a subreport, the top-level element of the subreport becomes an immediate child of the top-level element of the report.
The following figure illustrates how the report names of the main report and subreport appear in the generated XML:
Main Report and Subreport Names in XML
The XData ReportDisplay block in MyReportMainDef.cls formats the report, which produces a summary instead of the detailed report produced by MyReport.cls. The name attribute of the <report> element must match the name used in the ReportDefinition block. You must also add a <group> element to the ReportDisplay block, as an immediate child of the <body> element. The name attribute of this <group> must match the name attribute of the <report> element of the subreport.
The following figure shows how the name attribute of the <report> element refers to the XML element from the main report, and the name attribute of the <group> refers to the XML element from the subreport. The ReportDisplay block resolves subsequent XPath references in the context of the Report/myReport structure, as described in section Groups, Fields, and XPath Expressions.”
Main Report and Subreport Names in ReportDisplay
Using the <call> element with parameters
When you use the <call> element, you can also pass parameters to the method which returns the stream. The mechanism is similar to the use of parameters in the <report> or <group> sql property. The parameters are passed by reference in an array. The array is indexed by the positive integers 1, 2, 3 ... n where n is the number of parameters. For example, the following report passes a parameter to the method MyMethod. The value of the parameter is supplied by the field SalesRep in the resultset of the report:
<report xmlns="http://www.intersystems.com/zen/report/definition"
 name='Report' sql="SELECT SalesRep FROM ZENApp_Report.Invoice ">
 <call method="MyMethod"> 
  <parameter field="SalesRep"/>
 </call>
</report>
When you use a parameter field or expression, one that requires the setting of a field, then the field used is from the last row of the surrounding group or report, depending on where the call is nested. This is a subtle point that can cause unexpected results.
The method MyMethod adds the value of the parameter, in this case the name of the SalesRep, to XML it generates:
Method MyMethod(ByRef pParms) As %GlobalCharacterStream
{
 s stream=##class(%GlobalCharacterStream).%New()
 do stream.Write("<A12>")
 do stream.Write(pParms(1))
 do stream.Write("</A12>")
 quit stream
}
If the property hasStatus is true, the method can also send back a status. The status is passed back as the last
<report xmlns="http://www.intersystems.com/zen/report/definition"
 name='Report' sql="SELECT SalesRep FROM ZENApp_Report.Invoice ">
 <call method="MyMethod" hasStatus="true"> 
  <parameter field="SalesRep"/>
 </call>
</report>
Method MyMethod(ByRef pParms, Output pStatus) As %GlobalCharacterStream
{
 s stream=##class(%GlobalCharacterStream).%New()
 do stream.Write("<A12>")
 do stream.Write(pParms(1))
 do stream.Write("</A12>")
 Set pStatus=$$$ERROR(9999,"Deliberate Error")
 quit stream
}
<callelement>
Like <element>, <callelement> can take filter, expression, field and fields attributes. It is aware of the data context where it is used, and repeats for each record in the calling SQL. Unlike <element>, it calls a method, passes the value of expression, or the value of field if there is no expression, to the called method. Like the <call> element, <callelement> puts the output of the method in the generated XML at the place where the element occurs.
When you use <callelement>, you are responsible for ensuring that the outputted stream contains properly-escaped values. Be aware that the escaping selected by the escape attribute is applied to the data values before they are passed to the <callelement>'s method. So, if you are entering COS expressions or values, you may wish to set escape="passthru". You must determine whether or not XML-escaping is desired on a case-by-case basis.
<callelement> has the following attributes.
Attribute Description
escape
Browsers generally remove what they regard as excess white space from pages that they display. Therefore, if you want to retain white space characters in the output you must use the escape attribute. Note that the escape style is applied to the data values before they are passed to the method specified for the <callelement>.
escape has the following possible values:
  • "xml" — (Default) The text is XML escaped. This means that spaces are visible in the XML source, but do not appear in the display unless you set the literalSpaces attribute for the corresponding <item>. Zen reports strip out carriage return (ASCII 13) characters when processing the XML source data for a report.
  • "none" — All characters are preserved regardless of whether or not the original text contains spaces or newline characters. No XML escaping takes place, and all characters are enclosed in CDATA syntax. Zen reports do not strip out carriage return (ASCII 13) characters.
  • "noneifspace" — Any text that contains line feed or space characters is enclosed in CDATA syntax. Zen reports strip out carriage return (ASCII 13) characters when processing the XML source data for a report.
  • "passthru" — No XML escaping takes place. To keep the XML document valid, the XML data inside the element must be valid. For example, every opening element tag such as <foo> must be matched by a closing element tag </foo>. Zen reports do not strip out carriage return (ASCII 13) characters.
expression
Optional ObjectScript expression that can either be applied to the value of this item, supplied as %val, or provide an arbitrary value for this item. If present, this value is sent to the called method.
field Name of the field (column) in the base query for this report that supplies the value for this item. If this starts with a ! (exclamation point) then this is an expression that evaluates to field name. If there is no expression, this value is sent to the called method.
fields Name of fields (columns) in the base query for this report that supply the values for this item.
filter
ObjectScript expression that may or may not evaluate to 0 (false). When the filter expression evaluates to 0, Zen skips processing this <callelement>, and as a result, no output from this <callelement> appears in the XML data for the report.
In the filter expression, you can refer to the values of fields from the resultset query using the %val variable. You can use:
  • Single-valued %val to represent the value of the field identified by field attribute for this <element>
  • %val subscripted with the case sensitive names listed in the fields attribute for this <callelement>
For details, see The %val Variable section.
A general knowledge of ObjectScript is helpful in knowing how to construct these expressions. In addition to the ObjectScript tips in The %val Variable section, see Using Caché ObjectScript, particularly the String Relational Operators section in the chapter Operators and Expressions.”
method Name of a method that returns an XML stream. The stream is included in the generated XML at the location where the <callelement> element occurs.
The output of the method must be well-formed XML. The method can call a Zen report with information from the field or expression, but it does not have to. It can modify the data passed to it to produce well formed XML.
The following example uses the Cinema database in the SAMPLES namespace to illustrate <callelement>. The ReportDefinition block uses <callelement> to call a method once for each theater:
XData ReportDefinition 
  [ XMLNamespace = "http://www.intersystems.com/zen/report/definition" ]
{
<report
 xmlns="http://www.intersystems.com/zen/report/definition"
 name="FromTheater"
 sql="Select ID, TheaterName from Cinema.Theater">
<parameter expression='..ID'/>
  <group name="Theater">
    <element name="ID" field="ID" />
    <element name="TheaterName" field="TheaterName"/> 
    <callelement method="MyMethod" field="ID"/> 
  </group>
</report>
}
The method MyMethod uses the subreport ZENrCall.ShowByTime:
Method MyMethod(Theater) As %GlobalCharacterStream
{
    s stream=##class(%GlobalCharacterStream).%New()
    s rpt=##class(ZENrCall.ShowByTime).%New()
    s rpt.Theater=Theater
    s tSC=rpt.GenerateStream(.stream,0)
    i $$$ISERR(tSC) set stream=""
    quit stream
}
Note that the method sets the Theater property of the subreport to the current theater. The subreport finds films showing at that theater, and their show times:
XData ReportDefinition 
  [ XMLNamespace = "http://www.intersystems.com/zen/report/definition" ]
{
<report
 xmlns="http://www.intersystems.com/zen/report/definition"
 name="FromShow"
 sql="Select Theater, StartTime, Film from Cinema.Show 
      where (Theater = ?) order by StartTime"
 >
<parameter expression='..Theater'/>
  <group name="Show">
    <element name="StartTime" field="StartTime" />
    <element name="Film" field="Film" />
  </group>
</report>
}
The ReportDisplay block formats the resulting XML into a report that lists each theater and the films showing there sorted by show time.
<report xmlns="http://www.intersystems.com/zen/report/display"
 name="FromTheater">
 <body>
  <group name="Theater" pagebreak="true">
   <item field="TheaterName" width="2in"></item>
   <group name="FromShow">
    <table orient="col" group="Show" class='table2'>
     <item field="StartTime">
      <caption value="StartTime" width="1in" />
     </item>
     <item field="Film">
      <caption value="Film" width="3.5in" />
     </item>
    </table>
   </group>
  </group>
 </body>
</report>
<include>
You can place a set of XML statements into an XData block in your Zen report class or in any other class, and then reference that XData block from an XData ReportDefinition using the <include> element. Doing this inserts the contents of your XData block into the generated XML data source for the report. The report property suppressRootTag can be useful with <include> if the included data has its own root tag.
<include> has the following attributes:
Attribute Description
class Package and class name of the class that contains the XData block to be included. If not supplied, the default is the class in which the <include> element appears.
xdata Name of the XData block. This name is case-sensitive.
Suppose you add the following XData block to a class called My.Class.cls:
XData FloodInfo { 
  <Flood Type="Total">
    <Central>38</Central>
    <East>609</East>
    <Midwest>210</Midwest>
    <Northeast>70</Northeast>
    <Total>927</Total>
  </Flood>
}
You can then place an <include> statement in the XData ReportDefinition block of a class called Your.Class.cls as follows:
XData ReportDefinition 
      [ XMLNamespace = "http://www.intersystems.com/zen/report/definition" ]
{ 
<report xmlns="http://www.intersystems.com/zen/report/definition" 
        name="root" sql="SELECT TOP 1 ID FROM InsurancePolicies"> 
  <group name="InsurancePolicies" 
         sql="SELECT Location,InsuredValue 
              FROM InsurancePolicies 
              ORDER BY Location"> 
    <group name="Location" breakOnField="Location" > 
      <attribute name="Location1" field="Location"/> 
      <aggregate name="TotalLocation" field="InsuredValue" type="SUM" />
    </group> 
    <aggregate name="Total" field="insuredvalue" 
               type="SUM" format="$fnumber(%val,",")"/> 
    <include class="My.Class" xdata="FloodInfo"/> 
  </group> 
  </report> 
}
<macrodef>
You can place a set of ReportDefinition building-blocks into an XData block in your Zen report class or in any other class, and then reference that XData block from an XData ReportDefinition using the <macrodef> element. You must also set Parameter SUPPORTMACROS=1;. Doing this inserts the contents of your XData block into the ReportDefinition for the report. The XML inserted by <macrodef> is interpreted as if it had been entered directly in the ReportDefinition.
<macrodef> has the following attributes:
Attribute Description
class Package and class name of the class that contains the XData block to be included. This attribute is required.
xdata Name of the XData block. This name is case-sensitive.
Modify the report ZENApp.MyReport in the SAMPLES database in the following way. Create an XData block called Record:
XData Record { 
 <group name="record" 
   <attribute name='id' field='ID' />
   <attribute name='number' field='Num' />
   <element name='date' field='SaleDate' />
   <element name='customer' field='Customer' />
 </group
 }
Then place a <macrodef> statement in the XData ReportDefinition block of ZENApp.MyReport as follows:
XData ReportDefinition 
 [ XMLNamespace = "http://www.intersystems.com/zen/report/definition" ]
{ 
<report 
 xmlns="http://www.intersystems.com/zen/report/definition"
 name='myReport'
 sql="SELECT ID,Customer,Num,SalesRep,SaleDate 
   FROM ZENApp_Report.Invoice
   WHERE (Month(SaleDate) = ?) OR (? IS NULL)
   ORDER BY SalesRep,SaleDate" 
 <parameter expression='..Month'/>
 <parameter expression='..Month'/>
 <attribute name='runTime' expression='$ZDT($H,3)' />
 <attribute name='runBy' expression='$UserName' />
 <attribute name='author' expression='..ReportAuthor' />
 <aggregate name='grandTotal' type="SUM" field='Num' />
 <attribute name='month' expression='..GetMonth()' />
 <group name='SalesRep' breakOnField='SalesRep'
   <attribute name='name' field='SalesRep' />
   <aggregate name='count' type="COUNT" field='Num' />
   <aggregate name='subtotal' type="SUM" field='Num' />
   <aggregate name='avg' type="AVG" field='Num' />
   <macrodef class="ZENApp.MyReportMACRO" xdata="Record" /> 
 </group>
</report>
}
The XML output by the modified report exactly duplicates the XML output by the original report.
<get>
It is useful to be able to include the generated XML from one report in a group generated by another report. This way you can combine the results of various Zen reports into a master report. This gives you flexibility and simplicity since each report does one thing well. Use the <get> element to reference the XML statements generated by the XData ReportDefinition block in another Zen report class.
<get> has the following required attributes.
Attribute Description
host Host name, usually the host name for the Caché installation or localhost.
port Port number, usually the Web server port number for the Caché installation.
url Taken together, the host, port, and url attributes identify where to get a block of XML statements to include in the XML data source for the report. The url string provides the remainder of a URI string that begins with host and port.
The host, port, and url combination may produce any string that resolves to a URI. The purpose of this combination is to identify a source of valid XML text. In some cases this may be a file, but usually the url completes the URI by providing the application path name, package name, and class name of a Zen report class. The idea is for Zen to process this class in XML output mode so that it generates XML statements. In this case the url value should also include the following query parameters:
The following sample <get> statement identifies a Zen report class. Suppose this <get> statement appears in the XData ReportDefinition block of Your.Class.cls. This <get> statement takes the full set of XML statements generated by My.Class.cls and inserts them into the XML data source for Your.Class.cls:
<get host="localhost" 
     port="57777" 
     url="/csp/ours/My.Class.cls?$MODE=xml&amp;$STRIPPI=1"/> 
Generating a Report from a Class Query
You can generate a complete Zen report class, including the XData ReportDefinition block that defines its XML data source, by asking the Zen report generator to generate a Zen report class from an ordinary ObjectScript class that has a query defined in it. The resulting Zen report has default layout and styling which you can adjust by editing the generated Zen report class.
As an example, consider the Sample.Person class in the SAMPLES namespace. Sample.Person defines a query called ByName that looks like this:
Query ByName(name As %String = "") As 
  %SQLQuery(CONTAINID = 1, SELECTMODE = "RUNTIME") 
  [ SqlName = SP_Sample_By_Name, SqlProc ]
{
  SELECT ID, Name, DOB, SSN
  FROM Sample.Person
  WHERE (Name %STARTSWITH :name)
  ORDER BY Name
}
You could invoke the Zen report generator to create a Zen report class from Sample.Person by issuing the following sequence of commands. The SET statement in the following example would normally appear all on one line. Here it appears on four lines for typesetting purposes:
 ZN "SAMPLES" 
 Set Status=
     ##class(%ZEN.Report.reportGenerator).Generate("myOwn.GeneratedReport",
     "Persons","Sample.Person","ByName",1,
     "GroupOption","SortOption","SortBy","SSN")
 If 'Status Do $system.Status.DecomposeStatus(Status,.Err) Write !,Err(Err) ;' 
 Write !,"Status="_Status 
 Kill
You can also use the GenerateForSQL() method to generate a Zen report class from an SQL query, by providing the query instead of the name of a class that contains the query.
The Generate() and GenerateForSQL() methods have the following arguments:
Argument Purpose Value Shown in the Example
className Package and class name for the generated Zen report class. myOwn.GeneratedReport
reportName Name of the Zen report. This becomes the name of the root element in the generated XML data for the report. Persons
queryClass Name of the ordinary ObjectScript class from which to generate the Zen report class. Used only by Generate. Sample.Person
sql String that provides the SQL query used in report generation. Used only by GenerateForSQL. Sample.Person
queryName Name of a query defined in the queryClass. ByName
sortandgroup If 1 (true) sorting and grouping code is generated and the next four arguments are used; when 0 (false) this does not occur and you may omit the next four arguments. The generated Zen report is much simpler without sorting and grouping. 1
GroupOption Name of the option that determines grouping. GroupOption
SortOption Name of the option that determined sorting of detail records. SortOption
SortBy Name of an internal option related to the SortOption. InterSystems recommends a value of SortBy, as long as no column in the query is named SortBy. SortBy
UniqueId Name of a column in the query. The Zen report generator uses the value in this column to decide whether or not two rows are different for “COUNT DISTINCT” purposes. If two records have the same value on this column they are considered equivalent and are not counted as distinct. SSN — the person’s Social Security number
Restructuring the ReportDefinition XML
In some situations it is easier to take the XML provided by the ReportDefinition and transform it via XSLT. The resulting XML becomes the input XML for the ReportDisplay block, which generates the report. The PDF, HTML, tiff, excel, xlsx, and displayxlsx output modes all support this type of XML transformation. The XSLT style sheet can call isc:evaluate. You can define an XSLT style sheet that performs the XML transformation in the following ways:
  1. Set xmlstylesheet, which is a property of %ZEN.Report.reportPage. This property specifies a stream containing the contents of the stylesheet.
  2. If xmlstylesheet is null, Zen reports looks for a method specified with the property getxmlstylesheet. This method returns the contents of the stylesheet as a stream. You can define an xmlstylesheetarg whose ZENURL is $XMLSTYLESHEETARG and this argument is passed to the getxmlstylesheet method.
  3. If getxmlstylesheet is null, Zen reports looks for the parameter XMLSTYLESHEET. This parameter specifies an absolute or relative URI that points to a location whose contents are an XML stylesheet. If you provide a relative URI, the file must be located in csp/namespace in the Caché installation directory. The file can be either an .xsl file or a .csp file.
Gathering Data in the ReportDisplay Block
Zen reports charts and tables provide alternatives to gathering data in the ReportDefinition block. The following sections describe these alternative approaches: