The logo is a pig framed between two angle brackets.

XProc 3.0 Tutorial

Iterations: <p:for-each/> and <p:viewport/>

When transforming XML, a common task is to iterate over a collection of nodes and perform computations on these nodes. With p:for-each and p:viewport, there are two different approaches for iterations in XProc.

  • With p:for-each, the selected nodes are processed as a sequence of individual documents. The output is this sequence of documents.
  • p:viewport processes the selected nodes and inserts the results back into the source document. The output is the document with the changed nodes.

To demonstrate p:for-each and p:viewport and also see the differences between the two, we will use this input document for both examples:

<?xml version="1.0" encoding="UTF-8"?>
<doc>
  <title>Rodents</title>
  <image href="squirrel.png"/>
  <caption>Squirrel</caption>          
  <image href="mouse.png"/>
  <caption>Mouse</caption>          
  <image href="guinea-pig.png"/>
  <caption>Guinea pigs</caption>          
</doc>

Iterations With <p:for-each/>

First, let’s process this document with p:for-each. The task is to number the figures consecutively with p:iteration-position(). The function computes the index of the current document in the sequence and we add this value as number attribute to the image element.

<?xml version="1.0" encoding="UTF-8"?>
<p:declare-step xmlns:p="http://www.w3.org/ns/xproc" version="3.0">
  
  <p:input port="source"/>
  
  <p:output port="result" sequence="true"/>
  
  <p:for-each>
    <p:with-input select="//image"/>
    <p:add-attribute attribute-name="number" 
                     attribute-value="{p:iteration-position()}"/>
  </p:for-each>
  
</p:declare-step>

Output

<?xml version="1.0" encoding="UTF-8"?>
<image number="1" href="squirrel.png"/>

<?xml version="1.0" encoding="UTF-8"?>
<image number="2" href="mouse.png"/>

<?xml version="1.0" encoding="UTF-8"?>
<image number="3" href="guinea-pig.png"/>

p:for-each produces a sequence of three documents. Please note that we added sequence="true" to the output port declaration to avoid an error. Another thing to notice is that we added p:with-input to select the input for <p:for-each. If we omit <p:with-input would always be the root element of each document that appears in the input.

Iterations With <p:viewport/>

Now let’s see what the same instruction would do within p:viewport. Please note that although p:for-each requires an XPath expression, p:viewport expects an XSLT matching pattern. Therefore, we can omit the slashes because every occurrence of image in the document is matched.

<?xml version="1.0" encoding="UTF-8"?>
<p:declare-step xmlns:p="http://www.w3.org/ns/xproc" version="3.0">
  
  <p:input port="source"/>
  
  <p:output port="result"/>
  
  <p:viewport match="image">
    <p:add-attribute attribute-name="number" 
                     attribute-value="{p:iteration-position()}"/>
  </p:viewport>
  
</p:declare-step>

Output

<?xml version="1.0" encoding="UTF-8"?>
<doc>
  <title>Rodents</title>
  <fig>
    <image number="1" href="squirrel.png"/>
    <caption>Squirrel</caption>
  </fig>
  <fig>
    <image number="2" href="mouse.png"/>
    <caption>Mouse</caption>
  </fig>
  <fig>
    <image number="3" href="guinea-pig.png"/>
    <caption>Guinea pigs</caption>
  </fig>
</doc>

In contrast to its counterpart, p:viewport copies the iteration results back into the document tree. If we were to add a p:with-input, the result tree would change to the document tree selected by the p:with-input.

Read more…