Cindy - use unmodified XML or HTML documents as templates.


  use Cindy;
  my $doc = get_html_doc('cindy.html');
  my $data = get_xml_doc('cindy.xml');
  my $descriptions = parse_cis('cindy.cjs');
  my $out = inject($data, $doc, $descriptions);
  print $out->toStringHTML();


Cindy does Content INjection into XML and HTML documents. The positions for the modifications as well as for the data are identified by xpath expressions. These are kept in a seperate file called a Content inJection Sheet. The syntax of this CJS file (the ending .cis implies a japanese charset in the apache defaults) remotely resembles CSS. The actions for content modification are those implemented by TAL.

If you want to use Cindy for web development you will probably need Cindy::Apache2 (see which is distributed separately since it introduces additional dependencies.


The syntax for content injection sheets is pretty simple. In most cases it is

  <source path> <action> <target path> ;

If the syntax for an action differs from the above this is documented with the action.

The source and target path are xpath expressions by default. The action describes how to move the data. The whitespace before the terminating ; is required, since xpath expressions may end with a colon. The xpath expressions must not contain whitespaces. Alternatively they can be enclosed in double quotes.

Everything from a ; to the end of the line is ignored and can be used for comments.

A first line

  use css ;

switches the interpretation of source and target path from xpath to CSS selectors. These are less powerful but according to Parr (see this can be considered a good thing. Using css selectors reduces Cindies entanglement index from 4 to 1.


Actions locate data and document nodes and perform an operation that creates a modified document.

All source paths for actions other than repeat should locate one node. Otherwise the action is executed for all source nodes on the same target. The action is executed for all target nodes.

Actions are executed in the order they appear in the sheet. Subsheets are executed after the enclosing sheet.

The following example illustrates the effect of exectuion order. If a target node is omitted, an action that changes its content will not have any effect.

  true()    omit-tag  <target> ;
  <source>  content   <target> ;

So the above is not equvalent to the replace action.

Execution matches document nodes and then data nodes. Thereafter the actions are executed. Since execution of a repeat action copies the document node for each repetition, changes to this node done after the repeat are lost. At last this is recursively done for all subsheets.

As an unfortunate consequence matches on subsheet doc nodes do see the changes done by actions from enclosing sheets. This behaviour will hopefully change in future releases.


All child nodes of the target node are replaced by child nodes of the source node. This means that the text of the source tag with all tags it contains replaces the content of the target tag. If data is not a node, it is treated as text.

If no source node matched, the target node will be left unchanged.


The child nodes of the source node replace the target node and all its content. This means that the target tag including any content is replaced by the content of the source tag. This is equivalent to

  <source>  content   <target> ;
  true()    omit-tag  <target> ;

If no source node matched, the target node will be left unchanged.


The source node with all its content replaces the target node and all its content. This means that the target tag including any content is replaced by the the source tag and its content.

If no source node matched, the target node will be left unchanged.

Be aware that this requires the source tag to be valid in the target document.


The source node is used as a condition. If it exists and if its text content evaluates to true the target node is replaced by its children. This means that if the source tag exists and its content is not '' or 0 the target tag is removed while its content remains.


The source nodes content is moved into a comment node. This comment node is appended to the children of the target node. This can be useful for debugging and enables injection of SSI directives.


The syntax has an additional field atname

  <source>  attribute   <target> <atname> ;

that holds the name of the attribute. If the source node exists, its content replaces or sets the value of the atname attribute of the target node. If the source node does not exist the attribute atname is removed from the target node.


The source node is used as a condition. If it exists and if its text content evaluates to true nothing is done. Otherwise the target node and its children are removed. This means that the target tag is removed if the source tage does not exist or contains '', 0 or 0.0 while it is left untouched otherwise.


The repeat action is the CJS equivalent of a template engines loop. For each match of the source path the source node and the target node are used as root nodes for a sequence of actions. The syntax is

  <source>  repeat   <target>  [condition] {
  } ;

The optional condition is an xpath expression that is run in the context of the root node of a temporary document fragment. The fragment has two children, DOC and DATA which hold a subtree from a repeat doc respective data match. Only those combinations where the condition evaluates to true are used, all others are discarded.

Note that the repeat condition is an EXPERIMENTAL feature, it may well change.


A small number of additional XPath functions have been implemented.


This returns the context node. It behaves like the identically named XSLT function.


As a default Cindy dies on errors. Currently there are no warnings. Cindy detects log4perl and uses it for trace logging with levels DEBUG and INFO. If Cindy is used from Cindy-Apache2 the apache log is used instead.


Joachim Zobel <>


See Cindy/CJSGrammar.rdc for the RecDescent grammar for content injection sheets.

If you prefer a classic push template engine, that uses an API to fill the template from within the application see This also uses xpath or css selectors to move data into unmodified templates.