Iteration & Grouping

Iteration & Grouping

When working with repeated data, XPath enables you to loop through multiple elements or extract specific subsets of data based on conditions.

This section covers repeat loops, grouping techniques, and handling warnings when no nodes are selected.

Avoiding warnings in job logs when repeats select no nodes

Iterate across multiple detail lines (repeat).

Creating a repeat loop for a subset of nodes (based on a unique attribute, node name, or subtree value)

Grouping nodes (e.g., summarizing values from grouped nodes)

List values of all nodes, with a generic node name (starting with xxx).

Identifying the current line number within a repeat (e.g., in a transformation)

Avoiding warnings in job logs when repeats select no nodes

 

In the job logs you might see this warning:
Template.001014: Invalid element [id10] 'Repeat' expression evaluated to 0 nodes:XML/JSON

  

If you want to avoid such warnings, then you can consider to add a condition to test if there are any nodes for the repeat like below:

Change this:


 

And add a condition either like this:


 

The boolean() function returns true() if there is at least one node with that path. This is the most efficient test.


The alternative (less efficient way) is to count the number of nodes and compare that with zero like below:


  

Iterate across multiple detail lines (repeat).
If you insert this Repeat Element, then InterFormNG2 will run the elements inside of the Repeat loop for each Document node in the XML file:

  

 

  

You can then inside of the Repeat loop refer to the DocumentNo inside each Document node with an Xpath like this in a text element:



Notes
Please note, that both DocumentNo and ./DocumentNo will work in the rendered result, but the result view will also show the data of the first node, if you specify ./Document as above.

 

This will retrieve the values ‘1004', '1001' and '1003'.

 

Creating a repeat loop for a subset of nodes (based on a unique attribute, node name, or subtree value)


You do not need to include all the XML nodes with the same name/path in a repeat loop. You can also subset the nodes for the repeat e.g. based on attributes and/or the subtree of each of the nodes.

 

If we e.g. consider this XML file:

<persons>
  <chairman gender="female">
    <name>Maggie Frederiksen</name>
  </chairman>
  <person gender="female">
    <name>The Little Mermaid</name>
  </person>
  <person gender="male">
    <name>Mr. Andersson</name>
  </person>
  <person gender="male">
    <name>Mr. Nilsson</name>
  </person>
  <person gender="female">
    <name>Mercedes Hansen</name>
  </person>
  <person gender="female">
    <name>Lada Jaskagidaj</name>
  </person>
  <person gender="N/A">
    <name>Abcd Jensen</name>
  </person>
</persons>

 

Then we can e.g. define a repeat loop, that lists the name of all female persons (a gender attribute that is female) with this repeat loop: 


  

We can also condition the subset on the contents of the subset of the current node. We can e.g. list all persons that has the text 'sen' somewhere in the name node like so:


  

We can also condition a node set on the name of the nodes. If we e.g. have list of multiple nodes named "Adress" followed by a number (in sequence) like this:


  

Then we can build a repeat loop to go all of these "Address" nodes like so:

/Root/Document/*[contains(name(),'Adress')]

 

If we want to print out the contents of all the Adress nodes the full setup can look like this:


  

Where * selects any sub-node of the Document node and the name() function returns the name of the current node. The contains function is described here.

Grouping nodes (e.g., summarizing values from grouped nodes)
This section shows how you can group nodes in an XML file e.g. to only display the unique values/groups and e.g. summarize values from each group.
For this example we can use an XML file, that looks like this:
<Root>
    <CompanyInfo></CompanyInfo>
    <Greeting>HELLO WORLD</Greeting>
    <Barcode>www.interformng.com</Barcode>
    <Document Company="Herring Marine Research">
        <Contact_person>Martin Merman</Contact_person>
        <Date>20-09-2011</Date>
        <DocumentNo>1004</DocumentNo>
        <Adress1>Seaweed Street 14</Adress1>
        <Adress2>9000 Battleaxe</Adress2>
        <Country>DK-Denmark</Country>
        <Reference>PMK</Reference>
        <Intro></Intro>
        <Paragraphs></Paragraphs>
        <DetailLine SEQ="1">
            <Product>Interword400</Product>
            <Model>510</Model>
            <i-Group>i300</i-Group>
            <SN>44A2971</SN>
            <LicenseCode>81 20 01 3A</LicenseCode>
        </DetailLine>
        <DetailLine SEQ="2"></DetailLine>
    </Document>
    <Document Company="Florence Flowers">
    </Document>
</Root>

In the XML file above there are multiple Documents (in the Document nodes) and each document can contain multiple DetailLine nodes. The DetailLine nodes are the onces, that we want to list in this section.
Each DetailLine contains a node called Product and a node called Model.
For one of the documents we find these valus in the DetailLine:

    
But now we only to list each unique product once and then sum up all of the models for this unique product. So we want to list Swiftview(50) and InterExcel400 once, but also only include InterForm400 once with the models summed up (515+810+890+520+530+270).

So how can we do that?
For that there are two solutions:
  • Preceding-sibling. We can do a repeat of all detail lines and use XPath functions, preceding-sibling or following-subling to count the number of nodes before or after the current node (with the same unique selection).

  • Distinct-values. We can use the Xpath function, distinct-values to generate a list of all unique values, count this number and iterate across each of these unique values


Preceding-sibling
One way is to iterate across of all of the detail lines (of the current document) and then only list the detail line, if this is the first detail line, that contains the current product name.

This can be done with the template below:

   
The setup is described below - line for line:
  1. Go through each of the Document nodes in the XML file with a repeat.
  2. We insert a new page for each document.
  3. A table is inserted with the headers for the table (this should/could be inserted in a page header to handle page overflows).
  4. A repeat loop iterates through the DetailLine nodes of the current document.
  5. We copy the value of the product of the current DetailLine into a variable, product (case sensitive).
  6. We calculate the number of preceding-siblings (DetailLines prior to the current one), that has the same product name as the current one. If there are no earlier ones (i.e. the number is 0), then we know, that this is the first occurrence of a unique product and then we want to output the current detail line. In this test we use the product variable from above ($product).
  7. We insert a table to output the values of the current detail line.
  8. Inside the table we print out the sum of all of the Model nodes for the detail lines, that has the same product name as the current one.
The expression: count(preceding-sibling::DetailLine[Product=$product])=0  tests the number of previous detail nodes with the same product name.
The result of the setup looks like this:

   

The expression: count(following-sibling::DetailLine[Product=$product])=0 tests, if the current node is the last (instead of the first) detail line with the specific product name.
The result of the setup looks like this:


Distinct-values
If you prefer to use the Xpath function, distinct-values, then then solution can look like below:

  
The setup is described below - line for line:
  1. Go through each of the Document nodes in the XML file with a repeat.
  2. Set the variable, count to 1 to start a new document. We use this variable to keep track of the current index of the distinct values.
  3. The repeat loop iterates for the number of distinct-value of the product. The distinct-values returns a list of distinct (unique) values and the count function is used for counting how many unique values, that we have found. So if e.g. 4 unique values are found, then the repeat loop will run 4 times.
  4. Next we update a variable, product to the product name of the current distinct value, that we intend to print out.
  5. Then we insert the table that is to contain the output values.
  6. In cell one we want to output the value of the @SEQ node, that has the same product name, as the one we are currently working with. We select that by comparing the value of the product inside the DetailLines with the currently selected Product name, which is stored in the variable called product.
  7. In the column, where we want to print out the product we simply use the variable.
  8. In the column, that should print out the sum of all Models we select a node set of all Models found in DetailLine nodes, that has a product that equals the currently selected product.
  9. In the bottom we increase the counter for each iteration, when we go through each distinct value.
List values of all nodes, with a generic node name (starting with xxx).
It is possible to list the all nodes, that has a name starting with a specific text. An example of a relevant XML file can be this:


In this example we want to list the values of all the nodes, that starts with Adress and we do not know how many there can be.

A way to do that is to use a repeat loop with this specification:
/Root/Document/*[starts-with(name(),'Adress')]

In the example above the repeat loop goes through all nodes with the path/name: /Root/Document/Adress* and with the text() specification we print out the value of each node.

In this example the output is:

  
Identifying the current line number within a repeat (e.g., in a transformation)

In a transformation you cannot use a variable to keep track of the current node number, so if you e.g. within a repeat wants to calculate the current node number, then you can consider this expression:

count(preceding-sibling::*) + 1

This is e.g. relevant in a transformation, where you cannot use a variable to keep track of this node number - especially if you want to verify the current line node number in a repeat for the line nodes of a spooled file.







    • Related Articles

    • XPath

      In InterFormNG2, you use the language XPath for referencing data from the XML file. There are many sources of information if you want to learn more about XPath, but you can also simply read the few examples below to get a good idea of how it can be ...
    • Numeric Operations & Comparisons

      XPath supports various mathematical and logical operations. This section explains how to perform calculations, round numbers, compare values, and use Boolean logic in XPath expressions Solving the error: Unsuitable types for + operation (xs:string, ...
    • Split spool (fixed key position)

      If you want InterFormNG2 to process spooled files, that contains multiple documents e.g. multiple invoices within a single spooled file (e.g. generated by a batch invoice run), then you may want to split up the spooled file into multiple output PDF ...
    • Split spool (complex key)

      A spooled file can be split into smaller files depending on the contents e.g. to split up a larger spooled file with many invoices into individual invoices. If the other spooled file split functions are not enough, then you can consider this workflow ...
    • Split spool (variable key position)

      As the split spooled file (fixed key position) component above, this component can be used to split a spooled file based on a key value. However, in this case the key value can be in variable positions on the page. You just need to be able to find ...