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
.