ActivityManager Report Generator. Another bike, but in profile

ActivityManager is a reporting manager based on replacing template strings.
The main features of ActivityManager are:

  • Independence from the data source: all DBMSs are supported for which .Net providers exist, and not only;
  • Formation of templates without using COM: all reports are generated directly in XML;
  • Support for ods, odt, docx, xlsx report formats. Independence from the presence of a word processor on the user's computer: this feature follows from the previous one;
  • Presence of data pre-processing mechanisms: changing the format for presenting full names, monetary amounts, integers, real numbers and dates, including the possibility of changing the case in which the final data should be presented;
  • The presence of mechanisms for post-formatting data;
  • Ease of use and expansion thanks to a report configuration editor and simple plug-in architecture.

The report configuration using ActivityManager can be divided into 3 parts: data selection, processing and report generation directly. For detailed information on each of the stages, see the relevant sections.


  • Template file - an odt, ods, docx or xlsx file format that serves as a template for a future report
  • Template line - an element of a line of the form $ variable $ in the template file, which, when generating a report, will be replaced with inline data
  • The plugin is a regular .Net assembly that implements a special IPlug interface.
  • Action - from a technical point of view, this is a public method of a class that implements the IPlug interface and is visible through this interface
  • A step is a step of executing a sequence of actions described in the report configuration file. A step differs from an action in that the action is a description of the signature of what needs to be done at this step, and the step is a shell that associates the signature with specific parameter values

Configuration file structure

The report configuration file is a regular xml file of the following form:

    rudsn=registrySELECT * FROM executors;new_table

The bulk of the report configuration file is occupied by steps. The steps essentially represent the steps involved in generating a report: data sampling, data processing, report configuration, data source, and much more. The step consists of the name of the plugin, which contains the action action that must be performed, input and weekend parameters and the repeat parameter, indicating how many times this step should be performed (default is 1). Each input parameter has a name name and a value (content). Each output parameter has a default name name <and a name redefinition (content). Using this parameter name in the next steps of the report generation, you can get its value from the global parameters collection. For more information about the collection of global parameters, see the section "Transferring parameters".

At the top of the configuration file is. This block contains a list of downloadable plugins at runtime. Plugins are located by default in the plugins \ directory. Blockindicates the language in which messages will be displayed (mostly errors) during report generation. Two languages ​​are currently supported: Russian and English. Language translations are located in the lang \ directory.
Despite the simple structure of the configuration file, manually editing it is tedious. Therefore, a special configuration file editor was developed. More details in the section "Visual file editor"

Supplied plugins

The default delivery of ActivityManager contains 6 basic and one custom plugin:

  • SqlDataSource.dll - plugin for accessing SQL data sources;
  • TextDataSource.dll - plugin for accessing CSV data sources via SQL;
  • ConvertModule.dll - Data conversion plugin. This plugin contains the actions of converting the format for presenting data, cases, the actions of combining whole tables, columns and rows into a single report line, as well as selecting individual rows, cells from the data source table;
  • ReportModule.dll - the reporting plugin itself;
  • IOModule.dll - this plugin is designed for I / O and control the order of execution of steps. This module contains actions for launching a generated report in a word processor (and any other file or application), outputting debugging information to the console and in the MessageBox, conditional transition actions, due to which it becomes possible to make branches and loops in the report (for more details, see the section "Conditional redirect actions and JS macros");
  • JSModule.dll - this plugin is designed to write fairly simple JavaScript macros. The initial purpose of this plugin is to calculate the transition conditions implemented in the IOModule.dll module. But this plugin is certainly not limited to this functionality;
  • MenaModule.dll is a custom module that was developed as a specific extension of functionality for a specific housing change program in our organization. The plugin demonstrates how easy it is to add your functionality to the report generator if the base one is not enough to solve the tasks.

Passing parameters

Command line options

When starting report generation, the kernel (ActivityManager.exe) needs to pass one required config parameter in the form

ActivityManager.exe config="c:\1.xml"

As a result, ActivityManager.exe will read the configuration file located on the path c: \ 1.xml and generate a report.
In addition to the required config parameter, the ActivityManager can be passed the lang parameter, the language of messages displayed during execution (for the most part, errors that occurred during report generation).

In addition to the basic parameters config and lang, the ActivityManager can accept any number of any other parameters that will be placed in the collection of global runtime parameters and can be used during report generation. For instance:

ActivityManager.exe config="c:\1.xml" connectionString="dsn=registry" id_process=318

As a result of executing this command, the connectionString and id_process parameters will fall into the collection of global runtime parameters and they can be used during the connection to the data source, selection, and at any other step of the report. How to use these parameters at runtime, see the next section.

Global runtime parameters

The collection of global runtime parameters is a list of named variables that can be used at any step of the report generation (provided that at this step, the parameter is already available).
Parameters are written to this collection in two cases:

  • When transferring from the command line (see previous paragraph)
  • As a result of the previous steps of the report.

To use global parameters, you must enclose the parameter name in square brackets. Before passing the parameter value to execution, the preprocessor will check the correspondence of the template with square brackets to the parameters in the collection and if it finds a match by name, it will replace it.
For example, suppose we started report generation as follows

ActivityManager.exe config="c:\1.xml" connectionString="dsn=registry" id_process=318

Then, at run time, we can use the passed command line arguments as follows:

[connectionString]SELECT * FROM tenancy_processes WHERE id_process = [id_process]tenancy_processes[tenancy_processes]Row

As you can see, in the first step of SqlSetConnectionString (setting the connection string), [connectionString] is indicated in the input parameter, which tells the preprocessor that it is necessary to take this parameter from the collection of global parameters. If the parameter in square brackets is not found in the global parameter collection, no error messages will be generated. ActivityManager will understand that this line is not a parameter and will leave it unchanged.

Note the output parameter of the SqlSelectTable step


This record means that after the request is executed, the data from the source will be saved in the global parameter area under the name tenancy_processes and they can be accessed through [tenancy_processes] (which is done in the next step of ReportSetTableValue)


Replacement of parameters of simple types (strings, numbers, dates, etc., namely all types that can be uniquely converted from the string type System.String to the target using Convert.ChangeType) is performed in the substitution mode. Those. It is perfectly acceptable to specify a parameter as part of a value, and not as a whole. For instance:

SELECT * FROM tenancy_processes WHERE id_process = [id_process]

Parameters of more complex types that cannot be unambiguously converted from a string representation will be substituted directly. In such parameters, partial substitution is not possible by definition.

Substitution of parameters from the global collection is performed only in the input action parameters.

Data sampling

Before generating a report, you must obtain data. At the moment, DBMS for which there is a .Net provider, as well as text files in CSV format, can serve as data sources for ActivityManager. Unfortunately, there is no native data source for XML yet.

The result of fetching data from any of the described sources will be an object of either a scalar type or a ReportTable type.

SQL data sources

The SqlDataSource.dll plugin is designed to work with SQL data sources.
A simple example of using this plugin to fetch data from an ODBC data source:

dsn=registrySELECT * FROM executors;new_table

In the first step of this example, a connection string is set up. The second one selects data from the executors table, the data is written to a table called new_table. After that, we can make various modifications on them through the ConvertModule.dll plugin or send directly to the report.
ODBC is used as the default provider in SqlDataSource, but any provider supported in .Net is allowed. To install the provider, use the SqlSetProvider function. For example, here's how to install the OLE DB provider:


Here, the name parameter is the full or short invariant name of the provider. There is no need to specify a fully invariant name, as name matching is not clear (by pattern). For example, instead of the fully invariant name for MS Sql Server (System.Data.SqlClient), you can simply specify:


or for example


The full list of supported providers depends on the machine on which the report is generated and can be obtained through the DbProviderFactories.GetFactoryClasses () method.
In addition to the SqlSetProvider and SqlSelectTable actions, there are 8 more actions in the SqlDataSource plugin. A complete list of them can be found on the project wiki

CSV data sources

In addition to working with the DBMS, ActivityManager supports the selection and modification of data from CSV files. In addition to Microsoft Text Driver through ODBC, the ability to select data using the built-in text driver based on MySQL syntax is supported for selecting data from CSV files.
For example, suppose we have two CSV files, users.csv and user_actions.csv, with the following contents:




We need to choose the number of actions for each user.
The configuration of the data sampling step from the CSV will look like this:


                SELECT a.surname,, a.patronymic, COUNT(*) AS record_count
                FROM [csv_path]users.csv a
                LEFT JOIN [csv_path]user_actions.csv b ON = b.id_user GROUP BY

The result of this query will be saved in the global parameter collection under the name table.
In addition to the TextSelectTable action, the TextDataSource plugin has 2 more actions: TextSelectScalar and TextModifyQuery, detailed information about which you can also find on the project wiki .

Data conversion

Often there is a need to format the data after sampling from the source and before writing it to the final report. There can be many reasons for this, from the lack of support at the DBMS level for any complex transformations, to the reluctance to "dirty" the SQL query with a lot of unnecessary conditions that turn it into an unreadable sheet.
For such data transformations, the ConvertModule.dll plugin is used in ActivityManager.
Here is a list of what you can do with the data using this plugin:

  • Get a separate row from the data source table by its number. In the future, the data in this line can also be transformed and sent as a collection of automatically named parameters to the report;
  • Get the value of a single cell from the data source table;
  • Merge into a single row a table, column or row of a data source into a text variable. To make it more clear, this is some analogue of GROUP_CONCAT from MySQL;
  • Convert integers and real numbers, as well as date and time into a complex text or combined representation with the possibility of declension in cases
  • Convert the name of the Russian language from the nominative case to any case form;
  • Convert the number into a representation of the monetary unit, including (if necessary) and in text form with the possibility of declination in cases

Consider the functions of converting the data format in more detail. All data conversion functions can be classified by the

type of data being converted:

  • Integer representations: ConvertIntToString, ConvertIntCellToString, ConvertIntColToString;
  • Representation of real numbers: ConvertFloatToString, ConvertFloatCellToString, ConvertFloatColToString;
  • Date and time representations: ConvertDateTimeToString, ConvertDateTimeCellToString, ConvertDateTimeColToString;
  • Amount Representations: ConvertCurrencyToString, ConvertCurrencyCellToString, ConvertCurrencyColToString;
  • Name representations: ConvertNameToCase, ConvertNameCellToCase, ConvertNameColToCase;

By type of input and output parameters:
  • Conversions over scalar values: ConvertIntToString, ConvertFloatToString, ConvertDateTimeToString, ConvertCurrencyToString, ConverNameToCase;
  • Conversions over cells of an object of type ReportRow (this object is the result of the GetRow action of the ConvertModule.dll plugin): ConvertIntCellToString, ConvertFloatCellToString, ConvertDateTimeCellToString, ConvertCurrencyCellToString, ConvertNameCellToCase;
  • Conversions over columns of an object of type ReportTable (this object is the result of the SqlSelectTable and TextSelectTable actions of the SqlDataSource.dll and TextDataSource.dll plugins, respectively): ConvertIntColToString, ConvertFloatColToString, ConvertDateTimeColToString, ConvertCurrencyColToTameCoTameToTameToCreamTolToTameCoTameToTameCoToTameToCreamTolToTame

Integer conversions

As mentioned above, the actions ConvertIntToString, ConvertIntCellToString, ConvertIntColToString are used to convert integers. They differ only in the type of input and output parameters; therefore, we will not consider each of them in detail. Consider a simple example of converting numbers to a text representation. Suppose we need to select some numerical values ​​from a database table, and then bring them to a text look according to the following rules: all numbers must be in the parent case, the endings of the textual representations of the numbers must be feminine, the numbers must be quantitative, not ordinal, the first letter of the resulting textual representations of numbers must be large. First we need to get the data that we will convert.


            SELECT 1 AS int_column
            SELECT 13013
            SELECT 55132111
            SELECT 1055132111

As a result of this action, an object of type ReportTable will be stored in the collection of global objects, containing one int_column column containing rows with the values ​​1, 13013, 55132111, 1055132111.

The conversion itself can be performed according to the requirements defined above as follows:


As a result, we get a table called table with the following meanings: “One,” “Thirteen thousand and thirteen,” “Fifty five million one hundred thirty two thousand one hundred eleven,” “One billion fifty five million one hundred thirty two thousand one hundred eleven.”

Note that the inTable and outTable parameters are of the same ReportTable type. Since in this case the output parameter has the same name as the input parameter, it simply replaces the input parameter in the global parameter collection. This way you can easily make conversion chains.

Real number conversions

To convert real numbers, the actions ConvertFloatToString, ConvertFloatCellToString, ConvertFloatColToString are used. Transformations of real numbers are in many ways similar to conversions of integers, except that when converting real numbers, you cannot specify gender and what kind of numeral (ordinal or quantitative) it is. Consider a simple example of converting the number 3.1415926 to a prepositional case:


As a result, the value “three point one million four hundred fifteen thousand nine hundred twenty six ten million” will be written in the words parameter. Despite the fact that System.Double is used as the input parameter for the conversion of a real number, the maximum size of the real part is limited to billions (9 decimal places). Technically, nothing prevents to do more, but in practice the need for such accuracy has not yet arisen.

Date and Time Conversions

ConvertDateTimeToString, ConvertDateTimeCellToString, ConvertDateTimeColToString are used to convert date and time. Like the previous groups of actions described in this section, the actions of working with the date and time are very similar to each other. However, unlike conversions of integers and real numbers, these actions do not use the case and gender parameters. Instead, the more flexible format parameter is used. Let us consider in more detail which format representations are supported by date conversion actions:

  • dd - day of the month as a number
  • ddx - day of the month as text
  • MM - month as a number
  • MMx - month as text
  • yy - year in double-digit format
  • yyx - year in double-digit format as text
  • yyyy - year in four-digit format
  • yyyyx - year in four-digit format as text
  • hh - time from 1 to 12 as a number
  • hhx — время от 1 до 12 в виде текста
  • HH — время от 0 до 23 в виде числа
  • HHx — время от 0 до 23 в виде текста
  • mm — минуты в виде числа
  • mmx — минуты в виде текста
  • ss — секунды в виде числа
  • ssx — секунды в виде текста

In all the above formats, the letter “x” means the first letter of the case n (Nominative), g (Genetive), d (Dative), a (Accusative), i (Instrumental), p (Prepositional).

As you can see, the possibilities for setting the date format are huge. Consider a few specific examples of formats and results. For the initial value of the date we take "1988-06-26 13:47:56" (or if you prefer, you can write it in the format "06/26/1988 13:47:56", it doesn’t matter):

Format: dd MMg yyyy years HHn mmn ssn
Result: June 26, 1988 thirteen hours forty seven minutes fifty six seconds
Format: ddn MMg yyyyg
Result: June twenty-sixth one thousand nine hundred eighty-eighth year
Format:dd.MM.yy (MMn yyyyg)
Result: 06/26/88 (June one thousand nine hundred eighty-eight)

In the xml format, the step configuration for converting the date and time from the last example will look like this:

26.06.1988 13:47:56dd.MM.yy (MMn yyyyg)false

Money Conversions

To convert the presentation of monetary amounts to an ActivityManager, use the actions CurrencyToString, CurrencyCellToString and CurrencyColToString. Like all previous groups of data transformation actions, these actions differ only in the types of objects on which the transformation is performed: scalar value, row and table. Here is a simple conversion example. Suppose that in the database the amounts are stored in a field of type decimal, the field has the name dept, and we need to generate a report based on them, in which the amount will be presented in the format of a number with delimiters between thousands, and the sum must be written in brackets in writing. We will skip the stage of selecting data from the database, as it was done in the “Data Selection” section. Directly transforming the column according to the task will look like this:

[table]deptRublenii,ff (nniin rn ffn kn)falsefalsetable

As a result of the conversion, we get a table whose dept column is converted from a number format to a text one. Moreover, all the values ​​in this column will have the form "3 101 203.03 (three million one hundred one thousand two hundred three rubles three kopecks)".
Consider the supported formats for converting sums in more detail:

  • ii - rubles (dollars, euros) as a number
  • ff - pennies (cents) as a number
  • iix - rubles (dollars, euros) as a string
  • ffx - pennies (cents) as a string
  • rx - the word "ruble" ("dollar", "euro") - which word will be chosen depends on the parameter currencyType
  • kx - the word "penny" ("cent") - which word will be selected depends on the parameter currencyType
  • nn is the word minus if the number is negative. If the number is positive, then an empty string. A space after the word minus is set automatically.
  • n - the sign "-", if the number is negative. If the number is positive, then the sign is not put. A space after the "-" sign is NOT automatically set.

In all the above formats, the letter “x” means the first letter of the case n (Nominative), g (Genetive), d (Dative), a (Accusative), i (Instrumental), p (Prepositional).

As with date and time formats, in the case of monetary amounts, it is possible to form a representation in the most unimaginable forms.

Full name conversions

There are frequent situations when there is a need for declension by cases of a surname, a name and a middle name in the generated report. The ActivityManager uses the well-known Padeg.dll library for declination. ConvertModule.dll actions for declension by case names are called ConvertNameToCase, ConvertNameCellToCase and ConvertNameColToCase. Using these actions is no more difficult than all other data conversion actions. For example, to convert the name "Sukhov Zigmund Eduardovich" you can use the following configuration:

Сухов Зигмунд Эдуардовичss nn ppGenitivewords

The result of this action will be the string "Sukhov Zygmund Eduardovich."
The format conversion format is very simple and supports the following key constructs:

  • ss - full representation of a surname
  • s - the first letter of the surname
  • nn - full name representation
  • n is the first letter of the name
  • pp - full patronymic
  • p is the first letter of the middle name

For example, if we need to convert “Sukhov Zigmund Eduardovich” to sign the document, we can use the np ss template (while indicating the nominative case in the textCase parameter) and get the string “Z.E. Sukhov. "

Data Merge Actions

In addition to the actions for converting the data presentation format, the ConvertModule.dll plugin has simple actions that allow you to combine the cells of the ReportTable and ReportRow objects into a line with the delimiters specified. These actions are called RowConcat, ColumnConcat, and TableConcat. The RowConcat action is used to combine all the cells of the ReportRow object into one line with the separator between the cell values. The ColumnConcat action allows you to combine all the cells of one column of the ReportTable. The TableConcat action allows you to combine all the cells of a ReportTable with the delimiters between the cells of one row and the rows. Usage example


As a result of the execution of the action according to the presented example, the string type myConcatedStr parameter will be written to the global parameters collection, the value of which will be all the data from the myTable table, united by the declared delimiters (rowSeparator - line separator, cellSeparator - cell separator).

Item Selection Actions

Member selection actions include GetRow and GetCell.

The GetRow action allows you to get an object of the ReportRow class from an object of the ReportTable class at the specified line number (numbering starts from zero). The object of the ReportRow class is necessary for setting group matching of template strings and values ​​(for more details, see the section "Report Generation"). It is important to remember that if there is no line with the specified number in the ReportTable object, an exception will be thrown for the index to exceed the range of possible values. An example of using the GetRow action:


The GetCell action is intended to obtain the scalar value of a single cell of the ReportTable object:


Line numbering in the GetCell action, as in GetRow, starts from scratch.

Report generation

After selecting and transforming the data, the next step is directly inserting the results into the report template file.
ActivityManager supports the ability to generate reports in odt, ods, docx and xlsx formats. Work with all of these formats is uniform and in terms of configuration is no different. Differences exist only in post-processing, but this will be written in the appropriate section. One of the main concepts was the rejection of the use of COM-technologies for generating reports due to their slowness, dependence on the installed word processor and poor portability to new versions. As a result, it was decided to generate reports interacting with XML. This approach sacrifices the flexibility inherent in COM for data formatting, which gives higher speed and complete independence from the installed word processor. In fact, the word processor may not be installed on the computer at all,

Creating a template file

Before setting up a configuration file for generating a report, you must create a report template file. A report template file is a regular file of the odt, ods, docx or xlsx format with a predefined structure and formatting and specified template lines. Substitution values ​​in the template file are escaped with dollar characters. For example: $ variable $, where variable is the name of the pattern string. To make it more clear, look at the image below.

There are 6 pattern strings defined in this template file: $ title $, $ date $, $ n $, $ snp $, $ money $, $ date $. How exactly these template strings will be replaced in the template file depends on the configuration.

Configuration setting

To work directly with the report template files themselves, ActivityManager provides the ReportModule.dll plugin. This plugin is very easy to use and defines only 5 actions.

The first of these actions will be ReportSetTemplateFile. This action is intended to set the path to the template file, on the basis of which the report will be generated. This file must already exist. Consider a configuration example for this action:


Here, the [reportPath] parameter determines the path to the folder in which the example.odt file is stored and transferred to the command line when calling ActivityManager.exe or installed at earlier steps. Of course, nothing prevents us from setting the absolute path to the template file, and then there will be no need to pass any additional parameters: Setting the path to the template file is a mandatory action before invoking the report generation action.

In addition to setting the path to the template file before generating the report, you must configure the mapping of template strings to the values ​​you want to replace them with. Three actions ReportSetStringValue, ReportSetStringValues, ReportSetTableValue are intended for this in the ReportModule.dll plugin. The first two steps are intended to replace scalarpattern strings. Let's consider them in order.

The ReportSetStringValue action explicitly sets the matching of the template string and the value by which it will be replaced. When the report generation starts, this value will be substituted for the specified template string throughout the template file. There is no need to specify the characters "$" in the name of the template string when using this action. Here is a simple example of using this action:

titleЗаголовок 123

As a result, when generating a report on the template file from the previous image, the following result will be obtained

It can be tedious to map each template string to the corresponding value from the data source separately. Yes, and before you install it, you must select a value from the ReportTable result set using the GetCell action. Although theoretically possible, doing so is not recommended. This action is for the most part intended to set complex calculated values ​​or values ​​passed on the command line. If you need to set scalar values ​​from a data source (for example, an RDBMS), the more convenient ReportSetStringValues ​​action is used. Only one parameter of type ReportRow is passed to this action. The action maps the names of columns (cells) to the names of template strings. To clarify, take the following example:

dsn=registrySELECT 'Заголовок 321' AS title, NOW() AS date[table]0[row]C:\example.odt[fileName]

In the first two steps of the example above, we set the connection string and select the ReportTable into a collection of global parameters. This object has the name table and is a table with one row and a column with the names title and date. After that, using the GetRow action, we select the ReportRow object from the ReportTable object, which is a row at index 0 (the first row) of the ReportTable object. After that, set the ReportRow object as a list of mappings of values ​​and template strings in the ReportSetStringValues ​​action. As a result of all these actions, a configuration will be formed that “says” that it is necessary to search the $ title $ and $ date $ strings in the template file and change them to the values ​​from the corresponding columns (cells) of the ReportRow row.

Please note that during the insertion of scalar values ​​in the fourth column of the table, the template string of the same name $ date $ was replaced. The fact is that the actions of matching template strings to the values ​​of ReportSetStringValue and ReportSetStringValues ​​do not make any assumptions about where the data is located: in a table, in a footer, in a floating text block or elsewhere, they simply perform a template replacement. One of the solutions to this problem is to rename one of the template strings. Another method will be described later in the section “Procedure for setting pattern string mappings”.

When batch setting pattern-string mappings using the ReportSetStringValues ​​action, it is not necessary at all for all the columns (cells) from the ReportRow row to match pattern strings in the file. Template strings that could not be found in the file during generation will simply be discarded. The converse is also true, if there are template strings in the template file that have no values ​​associated with them, then they simply will not be replaced. There will be no errors.

Both of the above steps to set pattern string and value mapping are used to insert scalar values. You cannot insert tabular data into a report using these steps.

To insert tabular data, use the ReportSetTableValue action. The signature of this action is as follows:


The table parameter is an object of type ReportTable, the data from which it is planned to insert into the template file. A match is found in the template file by the column names of the ReportTable. The order of the columns does not matter, but their presence matters. An important point in replacing tabular data is that the search for matching template strings is similar. The report generator makes the assumption that if he finds a block of XML markup of a document that satisfies xmlContractor (see xmlContractor below) and at the same time 50 or more percent of ReportTable columns correspond to template lines in the template file, then this is the required element and you need to perform the substitution. For example, suppose that we have in the template file the table shown earlier (in the first figure). The table has pattern strings $ n $, $ snp $, $ money $, $ date $. We pass the ReportTable object to the ReportSetTableValue action, which contains the columns n, snp, money, date, time, action, pay. As a result, a replacement will be made, as We managed to match four of the seven ReportTable columns to the pattern rows (more than 50% matches) located in the row of the table.

It is important to note that similarity matching relies on the number of columns found in the ReportTable object, and not in the final template file. That is, it is perfectly acceptable to insert values ​​from a ReportTable with two or even one column into a table row of a report file with four pattern rows.

The xmlContractor parameter has a special meaning for the ReportSetTableValue action. This parameter determines what types of elements should be searched in the template file to match the ReportTable object. And when substituting, objects of this type will be duplicated. There are 4 types of xmlContractor: Paragraph, Table, Row, Cell. Their difference is easier to demonstrate with an example. Consider a simple template file in which we want to replace the values

It has a table and paragraph. Template lines in the table and paragraph are decorated in a similar way. Setting xmlContractor to Row will search for rows in each of the existing tables in the template file to match the columns of the ReportTable object. When a row element satisfying the similarity is found, it will be taken as a template and duplicated as many times as there are lines in the ReportTable object with replacement of the template lines with the values ​​from the corresponding ReportTable lines. The result will look like the image below.

As you can see, the paragraph element was completely ignored, because it is not nested in any of the table row elements in the document.
If xmlContractor is set to Table, then we get the result shown below

At first glance, it may not be clear what exactly happened, but the principle is the same as with the strings. Only this time, the table elements are searched for compliance with the ReportTable object, and when such a table is found, it is copied for each row of the ReportTable object with the replacement of template rows. In the figure above we see three tables. One for each row of the ReportTable. The use of xmlContractor Table allows you to generate more complex reports where data is not row-wise: signature blocks, vertical data cards, and much more. It is worth noting that the demonstrated behavior of the xmlContractor Table is typical only for reports in odt and docx. When generating a report in ods with the xmlContractor Table installed, a sheet will be copied (if a match is found) for each row in the ReportTable object.
The image below shows the result of installing xmlContractor Paragraph

As expected, in this case, a paragraph was copied for each line in ReportTable. Using this xmlContractor is convenient to create lists.
xmlContractor Cell has special meaning for xlsx and ods table processor files. For word processor files, it makes no sense. For example, take the following template file in ods format:

After that, we use the ReportSetTableValue action, which sets up a table with one value column.
Using xmlContractor Row we get the expected result:

Rows of the ods table in which correspondence to the ReportTable was found (in this example there is only one row, but this is not necessary), were duplicated for each row of the ReportTable.

And this is how the result will look if you install xmlContractor Cell:

In this case, we searched for similarity and duplicated cells rather than rows. Since technically in the files of table processors the elements of the cells are inside the elements of the rows, then duplication is performed within the row to which they belong. This property allows generating reports with a variable number of columns in table processors.

And finally, the last pending action in the ReportModule.dll plugin is ReportGenerate. An example of the use of this action was given earlier.


This action is intended to generate a report. The fact is that all previously considered actions of the ReportModule.dll plugin configure the report generator, but do not directly generate it. Report generation occurs when the ReportGenerate action is called. That is why this action must be called after all the settings.. The output parameter of the ReportGenerate action is the path to the generated report file. ReportGenerate does not open the report file after generation, this should be remembered. The reason for this behavior is modularity. The report generator cannot know what you plan to do with the generated report. You might want to write a plugin to send a report by e-mail after generation, or to write it to an FTP server or something else. To perform the most expected action with the generated report (open it), an Activity is provided in the ActivityManager. It is called IOOpenFile and, as you might guess from the name, is located in the IOModule plugin. An example of its use is given below:


When this action is performed, the file will be opened in the program associated with the file extension. That is, if we generate a report, and neither OpenOffice, nor LibreOffice, nor MS Word, nothing but WordPad is installed on the computer, then the file will most likely open in it.

How to set pattern string mappings

When setting mappings of template strings to values ​​with the actions ReportSetStringValue, ReportSetStringValues, ReportSetTableValue it is important to understand that the replacement during report generation is performed in cascade in the order of their declaration. This means that parameter values ​​at an early step can serve as template parameter strings at a later step. Let's look at an example.

contentMy name is $name$. My surname is $surname$.nameVasilysurnameIgnatov

As you can see from this example, the template string $ content $ will be replaced first with the value “My name is $ name $. My surname is $ surname $. ". After that, the template strings $ name $ and $ surname $ will be replaced by “Vasily” and “Ignatov”, respectively. The resulting line in the template file will be “My name is Vasily. My surname is Ignatov. ". However, if you swap the steps and put the step that sets the value of the template string $ content $ to the very bottom, then the replacement of the template lines $ name $ and $ surname $ does not happen (these lines are still not there at this stage of report generation). As a result, the line “My name is $ name $. My surname is $ surname $. ". Cascade replacement allows you to generate reports with a strong dependence of the presentation of the data on the values ​​of the input parameters.

Post-processing the template file

Post-processing refers to the process of formatting a text presentation after filling out a template file with data. This is necessary if we have a task, for example, to highlight in bold part of the text after insertion. As a specific example, consider a housing exchange agreement. Under the agreement, the number of participants can be unlimited, but in the list of participants from each of the parties it is necessary to display them all in one paragraph (not a list), and it is known that his name and passport data are included in the information for each participant, but only the full name must be highlighted in bold. If lists could be used to solve this problem, then we would use ReportSetTableValue with xmlContractor = Paragraph set and would preset styles in the template file for each parameter. But since it is necessary to display all the participants in one paragraph, we can use the tags of post-processing. When fetching data, we combine all participants and their passport data into one line using GROUP_CONCAT in MySQL, or FOR XML in MSSQL, or TableConcat of the ConvertModule.dll plugin, or in some other way that is more familiar to you.

Moreover, when combining, we conclude the name in a special tag $ b $ value $ / b $. Those. the result is a line with the following content: "$ b $ Ignatov Vasily Vasilievich $ / b $, 06/29/1988, passport No. 0123456 series 4321 ..., $ b $ Ivanov Ivan Ivanovich $ / b $, 01.15. Born 1976, passport No. 654321 series 1234, ... ". After inserting this line as a normal value into the template file and after completing the replacement of all other template lines, a special post-handler will be launched. tags. It will replace all the tags $ b $ $ / b $ with bold style, i.e. all text inside these tags will become bold:

At the moment, post-processing is only supported in OpenOffice reports.
The following special post-processing tags exist:

  • $ b $ $ / b $ - all text in this tag will be bold
  • $ i $ $ / i $ - all text in this tag will be italicized
  • $u$ $/u$ — весь текст в этом тэге будет подчеркнутым
  • $br$ — тэг будет заменен на перенос строки с созданием нового абзаца (аналог Enter в OpenOffice)
  • $sbr$ — тэг будет заменен на перенос строки без создания нового абзаца (аналог Shift+Enter в OpenOffice)

Старайтесь не задавать шаблонные строки с именами из этого перечня.

Действия условного перенаправления и JS макросы


ActivityManager supports two actions for conditional redirection of execution order. Both of these actions are located in the IOModule.dll plugin. These include IOIfConditionToStep and IOIfConditionExit. As you can see from the action name, IOIfConditionToStep allows you to go to the specified step if the condition is true, and IOIfConditionExit completes the processing of the steps in the configuration file if the condition is true. Consider a specific example. Suppose we need to formulate agreements on social, commercial, and specialized tenancy. All three reports have a similar structure and the same (well, or almost the same, it doesn’t matter) template lines, and differ only slightly in the text of the agreement. For this reason, we do not need to create multiple configuration files. It’s enough to create several template files,

return [id_rent_type] != 1condition[condition]8\\nas\media$\ActivityManager\templates\registry\tenancy\agreement_commercial.odtreturn [id_rent_type] != 2condition[condition]11\\nas\media$\ActivityManager\templates\registry\tenancy\agreement_special.odtreturn [id_rent_type] != 3condition[condition]14\\nas\media$\ActivityManager\templates\registry\tenancy\agreement_social.odtreturn ([id_rent_type] == 1) || ([id_rent_type] == 2) || ([id_rent_type] == 3)condition[condition]18Вы пытаетесь распечатать соглашение на неподдерживаемый тип наймаtrue

The entire configuration presented above will look like this:

In the above configuration, in addition to the already mentioned actions IOIfConditionToStep and IOIfConditionExit, there is one more action that requires special attention. This is the JSRun action of the JSModule.dll plugin. This action allows you to implement microcomputations (increments, checking values, etc.) in the form of JavaScript macros. The first JSRun action parameter passes a regular JavaScript script. Since this script is plain text from the point of view of the core of the ActivityManger, it is possible to do any substitutions through it [parameters]. JSRun is an add-on to the famous Noesis.Javascript libraryand supports all syntax constructs supported by this library. To get the result from the script, you must return it with the return statement or by assigning the value to a special script environment variable named result.


In addition to conditions, the IOIfConditionToStep action can also implement cycles with JSRun. Suppose we have in the database information on users, their payments and dates of payment in the following form,

and we need to generate a report of the form.

In this case, the number of years, and therefore the tables, is not known in advance. In this report, payments are grouped by year and posted in different tables. Let's get started.

To generate this report, we will need such a simple template file.

First of all, it will be necessary to select the number of years from the database table, exactly how many tables will be generated during the generation of the report. The value itself will be needed to increment the loop.

After that, select a list of years and pattern mappings. So this data will look in the ReportTable object (let's call this table yearsTable):

The data presented above must be inserted using the ReportSetTableValue action with xmlClouser = Table in the template file. The result will be the blank shown below (you won’t see it during report generation)

And finally, you need to go through all the rows of the yearsTable table, select the year value from them with the GetCell action (let's call it currentYear) and execute a query for each row of the yearsTable table to the database presented below (example for MySQL)

SELECT @n := @n + 1 AS n[currentYear], snp AS snp[currentYear],
money AS money[currentYear], DATE_FORMAT(date,'%d.%m.%Y') AS date[currentYear]
FROM test, (SELECT @n := 0) v
WHERE YEAR(date) = '[currentYear]'

After that, you need to set the result of this query (at each iteration) with the ReportSetTableValue action with xmlClouser = Row in the template file.
A complete listing of the configuration file for this task is presented below:

                SELECT DISTINCT YEAR(date) AS year,
                    CONCAT('$n',YEAR(date),'$') AS n,
                    CONCAT('$snp',YEAR(date),'$') AS snp,
                    CONCAT('$money',YEAR(date),'$') AS money,
                    CONCAT('$date',YEAR(date),'$') AS date
                FROM test
                ORDER BY year;
                SELECT COUNT(*) AS count
                FROM (
                    SELECT DISTINCT YEAR(date) AS year
                    FROM test
                    ORDER BY year) v;
            countYearsTable[countYearsTable]0countcountYearsreturn [countYears] == 0condition[condition]15return [countYears]-1;countYears[yearsTable][countYears]yearcurrentYear
                SELECT @n := @n + 1 AS n[currentYear], 
                    snp AS snp[currentYear], 
                    money AS money[currentYear], 
                    DATE_FORMAT(date,'%d.%m.%Y') AS date[currentYear]
                FROM test, (SELECT @n := 0) v
                WHERE YEAR(date) = '[currentYear]';[table]Rowtrue8[fileName]

Visual editor of configuration files

As you could see from the previous sections, and especially from the section “Conditional redirection actions and JS macros”, writing an xml configuration manually is not only tedious, but also fraught with errors. In addition, you need to keep a lot of information in your head: what are the names of the parameters, how many are there, what type they are, what actions are there and in which plug-ins they are located, what is the serial number of the current step and much more. A visual editor has been developed to facilitate the creation of report configuration files. In the image below, you can see how this editor looks like with an open configuration file that demonstrates working with loops from the previous section.

The left side of the editor window contains a list of steps and their sequence numbers (which is very convenient when working with conditional redirection). It is in this order that the steps will be located in the xml-file. Below are 4 buttons, the purpose of which is intuitive (from left to right): add a step to the current position, delete the current highlighted step, move the highlighted step one position up, move the highlighted step one position down. It is not necessary to use buttons to move steps; drag'n'drop is supported.

Two drop-down lists are located in the right part of the editor window: “Plugins” (with a list of currently connected plugins) and “Actions” (with a list of actions available from this plugin). Also on the right side is a table with a list of input and output parameters. Each parameter displays a name, type, direction (input or output) and value. The first three columns do not need to be specified; they are automatically generated by reflection from plugin assemblies. Only values ​​are required in this table. Below, under the table with the parameters, there is a detailed description of the selected action. These descriptions are taken from the xml documentation files located with the plugins in the plugins / folder. You can read more about how these files are formed, for example, here .

Beginning of work

When creating a new configuration file, the first thing to do is connect the plugins that you plan to use. Otherwise, they will not be available in the drop-down list. This is done simply: Main menu -> Settings -> Plugins. As a result, a window will appear on the screen in which it is necessary to choose which plugins we want to use.

This completes the initial setup. After that, you can start creating the steps of the configuration file. After adding a new step, you must select the plugin and the action in this plugin that you want to perform at this step. After that, a list of parameters for this action will appear in the table of parameters. To set a value for an action parameter, double-click on it.
There are two editors for setting parameters. Which editor will be called is determined automatically by the type of parameter.
For parameters with a limited tuple of possible values ​​(enumeration, logical data type) and numbers, as well as for setting the names of output parameters, a simplified editor is called up.

In the drop-down list of this editor, you can select one of the possible values ​​visible at this stage or you can set an arbitrary value manually. The list will show all possible values ​​of the enumeration (loaded by reflection from the plugin assembly), all command line parameters and those output parameters of the previous execution steps that match the type of the parameter being set.
For Sytem.String data type parameters and complex data types, a ScintillaNET-based editor with JavaScript and SQL syntax highlighting is called.

In this editor, as well as in the simplified one, there is a drop-down list with the parameters from the global collection visible at this step. The Paste button inserts a parameter selected in the list at the cursor location in the editor.

Debugging features

The visual editor supports several simple features that allow you to debug the performance of configuration files without having to start the ActivityManager.exe kernel from the console or your application.

First of all, before starting the configuration file for execution, you need to set the command line parameters that you plan to transfer ActivityManager.exe at startup. This is done in the Main menu -> Settings -> Command Line Parameters. The window shown below will appear on the screen.

Note that you do not need to specify the required config parameter that is passed to ActivityManager.exe in this window. All parameters specified in this window will be available during the configuration of steps from the drop-down list of visible possible values.
After setting the command line parameters, you can launch the configuration file for execution. To do this, press the F5 key or go to the Main menu -> Configuration -> Run. If an error occurred during the execution of the configuration file, you will receive a message with information on which step the error occurred and the text of the error itself.

There are cases when there is not enough information on the error that has arisen to localize it, or there are no errors at all, but the output is a report that does not meet expectations. In such cases, the output of parameter values ​​through the IODebugMessage action of the IOModule plugin can help. This action takes only one parameter - the message that needs to be displayed. Casting the output objects to a string type before output is carried out by the ToString () method; therefore, for data types for which this method is not overridden, the debug window will only display the class name of the object.
After setting up the configuration file and checking that everything works correctly, you can copy the execution line, which you need to call the ActivityManager kernel to generate a report from your application or command line. To do this, go to the Main menu -> Configuration -> Copy execution line.

If possible, it is strongly recommended that you use a visual editor when creating report configuration files, rather than manually editing the xml files.

Writing your own plugins

Plugins are regular .Net assemblies located in the plugins \ directory and implemented according to a few simple rules:

  • IPlug public interface must be declared in the assembly
  • The IPlug interface must be in a namespace whose name matches the name of the assembly. That is, if we are writing a plugin whose assembly is named MenaModule.dll, then the name of the namespace in which the IPlug interface is located should be MenaModule.
  • The assembly should have only one class that implements the IPlug interface. Otherwise, which particular class will be visible is not defined (the first one found during reflection).
  • Only actions must be declared in the IPlug interface. The return parameter of these actions must be void. Output values ​​are returned through out parameters.

Consider the example of creating your own MyModule.dll plugin:

namespace MyModule {
    public interface IPlug { 
        void MyAction(string input, out string output); 
    public class MenaPlug: IPlug 
        public void MyAction(string input, out string output) 
        { output = input+input; 

That's all. After compilation, it remains to put this plugin in the plugins \ directory and ActivityManager with AMEditor will automatically find it. You can use the MyAction action of this plugin through the following configuration:


A slightly more complex example of a custom plug-in can be found in the MenaModule.cs file .

Note: in order to work in your plug-ins with objects of the ReportTable and ReportRow classes, you need to add a reference to the ExtendedTypes.dll assembly in the project.


The project has been developing for about a year and is used in the combat missions of our organization. Like any report generator, ActivityManager has both advantages and disadvantages. The advantages were described in the introduction, but now I would like to note the disadvantages:

  • Image insertion not supported
  • The ability to merge cells after inserting values ​​is not supported. Cells must be merged during template preparation. For this reason, it becomes problematic to create some particularly complex reports.
  • There are no unit tests (and not only unit). Despite the fact that during the year of use on combat projects, most of the functionality of the ActivityManager has been repeatedly tested, this is still a very serious but fixable flaw.

Despite these shortcomings, I hope that there are people who may find the project interesting. Maybe someone wants to use this report manager in their tasks, or maybe someone even wants to participate in its development. In any case, if I’m interested in at least one person with this post, then the post was written not in vain. =)


Also popular now: