FFC's XMLListener class provides a worked example of runCollector use, using information stored in Document Properties and other header-level MemberData entries exposed by the ReportBuilder, as an example of what you might want to store in the collection.
All MemberData items defined in a report in the header record carrying a Type value of "R" (FRX_BLDR_MEMBERDATATYPE)-- regardless of prefix or namespace -- are exposed in a new Run element, which follows the VFP-RDL metadata and Data results elements for each VFP-Report element in the XML document.
Each property child node of the Run element in the VFP-RDL schema has an id attribute, representing its original property name. The original name is concatenated with its original property namespace, if the namespace is different from the standard Advanced Property namespace, to ensure uniqueness of each item by id value.
The text value of the property node is the evaluated result of the property. If the property type is XML, the value is represented as true XML (not escaped or encoded text). The example in this docoid shows you the difference.
XMLListener forces the value of runCollectorResetLevel to the #DEFINEd value OUTPUTFX_RUNCOLLECTOR_RESET_ONREPORT, in keeping with the position of the appropriate nodes within the VFP-RDL schema, which hold the results on a per-report basis. Accordingly, XMLListener opts to handle this activity in AfterReport, just before finalizing the structure of the XML output document. At this time, all material that you might have collected into expressions or member properties representing "report run" information are available.
XMLListener implements _ReportListener's fillRunCollector and resetRunCollector methods, and calls them during AfterReport. Its fillRunCollector method goes through all the MemberData for the FRX header record.It treats each MemberData row according to the rules for Advanced Properties (even though some of them may not really be Advanced Properties), as follows:
- It does not not handle MemberData entries that have an empty value in ExecWhen, Execute, or Name columns. It also skips any DELETED() rows, in case you have dynamically deleted some MemberData cursor rows at runtime.
- It treats the ExecWhen Column as the property name. If the Name Column does not hold the namespace for Advanced Properties, it prefixes your ExecWhen value with your namespace, to ensure property name uniqueness.
- It treats the contents of the Execute column as the value of the property. It handles evaluation of each according to its type, as determined by the DeClass column. If it cannot determine the property type, it treats your Execute value as a literal.
|Refer to the Spacefold article on Data Visualization in Reports, Appendix A, for complete information on how Advanced Properties use MemberData columns, as well as on how other in-the-box features leverage MemberData columns in different ways, as suits their needs.|
XMLListener then calls an XML-specialized getRunNodeContents method to generate a set of XML nodes for its output.
HTMLListener's default XSLT transform examines the Run element for items it recognizes and treats each according to appropriate rules for whatever HTML feature the item represents. In some cases, such as Document.Title, the HTML output can use only one value if the value happens to be repeated multiple times in a chain. In other cases, such as Document.Keywords, HTMLListener can concatenate multiple results in the chain together. In still others, such as HTML.CSSFile, the HTML output can contain the same tag multiple times, as often as the same MemberData item appeared in the chained reports. Each reportlistener derived from XMLListener can interpret the contents of the Run node(s) in the RDL XML differently.
Each derived reportListener also has the ability to add more content into the Run node(s), by augmenting fillRunCollector with to add contents beyond the items supplied by their XMLListener ancestor class. HTMLListener provides an example of this ability, by recognizing and handling the complex HTML.Metatag.HTTP-EQUIV Document Property in a specialized way.
This property is stored as complex XML, which is specified by HTML Listener to require a "private" format based on standard VFP CursorToXML. Each row in the cursor-shaped XML represents a separate meta-tag for HTML use. HTMLListener decodes this format and creates a separate runCollector item for each separate meta-tag.
This HTML.Metatag.HTTP-EQUIV strategy was chosen not only to display the power of nesting XML within a single MemberData item and augmenting fillRunCollector, but also because HTTP-EQUIV <meta> tag syntax has many, many values that can be set. It wouldn't make sense to try to create a separate Advanced Property on the Document level for each possible HTTP-EQUIV setting that developers might want to use. Instead, a single HTTP-EQUIV compound structure is stored in a single Advanced Property and extracted by HTMLListener at runtime.
The sample code belows provides instructions on the proper XML structure for HTML.Metatag.HTTP-EQUIV values that HTMLListener can understand and decode. As you can see, you can create the appropriate XML easily with a simple cursor.
Although HTML.Metatag-HTTP-QUIV is HTMLListener-specifc,
this example is included in the current topic as an example of the type of thing
that your XMLListener-derived class could do for any type of attribute requiring
complex or compound content. We recommend simple, cursor-shaped or xmlAdapter-generated,
structures for your complex XML content to make it easy for developers using your
class to populate the property with contents.
You will find additional information about other HTML-specific Advanced Properties (on both Document and individual FRX control level) in the TMM Docoid on How to: Use Advanced HTML Properties in Reports.
Which type of runCollector should you use? A Table or Alias is obviously easy to implement, does not require the "key" for each row to be unique (XMLListener uses a combination of the MemberData items' namespace and property name strings to ensure uniqueness).
However, both a Collection or Empty object allow you to add serialized XML documents as the value of a single property if you like. As currently implemented, XMLListener's cursor-handling mode does not.
If you use an Empty object, the name of each attribute corresponds to the collection
item key, and of course the value of each attribute correspondes to the collection
If you use a cursor alias, getRunNodeContents looks through the cursor fields in order. It uses the first field it finds in the cursor that is of "M" or "C" type as the item key, and the second field it finds of the same types as the item value. Note that XMLListener will find the cursor in either the Current or the FRX data session. However, only the former session is useful if you want to share the runCollector cursor directly between reports, and even then, only if you are not using private data sessions for your reports.
There is a better way to share the results of each run. As discussed in the docoid on Using _ReportListener's RunCollector, you should publish a method to export the results of your runCollector to a format such as a cursor that is independent of its internal representation. The fact that, internally, you may be cloning one cursor into another cursor versus moving the contents of an Empty or Collection object into a cursor, should be unknown to the consumer of the cursor. The consumer's main concern is in which data session this exported cursor appears, and a known/published format for this target cursor, not the source format you used to derive it.
The TMM docoid on XMLListener includes an example showing how an Advanced Property of XML type appears, several times, in VFP RDL XML. (The property is named MyCustomDocPropertyOfXMLType in the example.) As you see there, the value appears in text-encoded versions within the standard VFP RDL content, because it appears first in the Style contents for an FRX row and then again as an attribute for a MemberData row. Finally, you can see that the decoded, true XML version of the property appears as mixed content for a custom property within Run node.
Of course, the MyCustomDocPropertyOfXMLType complex content has to be defined by somebody, and it has to be meaningful to some code somewhere, in order for that content to be of any use. HTML.Metatag-HTTP-EQUIV shows you a realistic implementation of such a property.
HTMLListener specifies a format for the XML that it requires in order to use this Document-level Property effectively. The following code shows you the creation of a cursor in the appropriate format, and how to derive the XML from it:
CREATE CURSOR meta (name m, content m, type i) * the three fields allow us to indicate: * name: what HTTP-EQUIV value we want; * content: what the output value is, and; * type: whether that value * has been expressed as a literal or expression.
INSERT INTO meta VALUES ("refresh","10",0) * above, a literal INSERT INTO meta VALUES (; "cache-control",; IIF(VARTYPE(_CACHETHIS)=[C], ; _CACHETHIS,[no-cache])",1) * above, an expression dependent on a variable named _CACHETHIS GO TOP CURSORTOXML(ALIAS(),"_CLIPTEXT",2,0,RECCOUNT()) * the system variable _CLIPTEXT now has what you need, * so you can examine it: MODI FILE && paste into the file to examine the XML
At this point you could also paste it into the appropriate Report Builder dialog for use in an FRX, as well as begin to think about how you might perform these same steps programmatically.
As described in TMM docoid on XMLListener, the results will appear several times in the resulting VFP-RDL, encoded, and they will also appear, as expected, as an property node within the Run element:
<Run> <property id="HTML.Metatag.HTTP-EQUIV"> <VFPData> <meta name="refresh" content="10" type="0" /> <meta name="cache-control" content="IIF(VARTYPE(_CACHETHIS)=[C],_CACHETHIS,[no-cache])" type="1" /> </VFPData>
</property> <property id="HTML.Metatag.HTTP-EQUIV.refresh">10</property>
<property id="HTML.Metatag.HTTP-EQUIV.cache-control">no-cache</property> </Run>
... but, as you see above, HTMLListener has also added some runCollection elements holding the evaluated version of each HTTP-EQUIV row.
To see the XML that HTMLListener receives after adding nodes into the runCollector
you can temporarily turn off the automatic application of its XSLT transformation:
_oreportoutput["5"].applyUserTransform = .F.
Your output file may still be named *.HTML by default if you haven't adjusted it, but it will be the interim XML file, untouched. It will contain anything extra that HTMLListener adds to the base VFP-RDL schema, including the extra contents in the Run node.
Adding custom content is a practice permitted by the VFP-RDL schema, according to a specified plan, in both the Run element and, with significant restrictions, throughout the rest of the XML document. This feature planned for discussion in a TMM docoid titled How to: Add Custom Attributes to VFP-RDL using XMLReportListener and its Derived Classes. You can see how it's done by looking at the XMLDisplayListener and HTMLClasses in the FFC. You can see where in the XML output the schema permits additional content by checking the VFP-RDL XSD, which ships with XSOURCE.ZIP in SP2.
HTMLListener's default XSLT reads these additional, evaluated rows to derive HTTP-EQUIV header elements for the HTML output document:
<meta content="10" http-equiv="refresh" > <meta content="no-cache" http-equiv="cache-control" >