Invenzzia » Resources / Articles / Managing XML in OPT / Full article

Managing XML in OPT

  • Published on 3 January 2010 09:14:00 GMT, updated over 4 years ago by Tomasz Jędrzejewski
  • pl

The Open Power Template syntax is based on XML. This leads us to a set of instructions helpful in managing the produced XML code, creating attributes and tags and the whole process is a bit different than in most of the template engines. This article is going to show you, how to use the available tools and how to manipulate the generated output HTML code.

Table of Contents

Open Power Template is one of a small group of template engines that use XML as the base of their syntax. Inspite of many pros, it implies some changes in the HTML code manipulating for the template designer. The syntax must follow the rules of XML, and such things as dynamic attribute generation or changing the tag name must be done differently than in other template engines, like PHP or Smarty. This article is going to help you understanding it and show you many tricks useful in many everyday situations.

1. XML requirements

Let's begin with a short repetition of an XML standard. This list should be in your head all the time, because many problems of the beginning OPT users are caused by forgetting they work with XML and importing solutions from other template engines that would never work here.

  1. Every XML tag must be closed. If our template contains an opening <div>, we must close it in the same template and in the same file: </div>.

  2. An XML document may contain one main tag and no more. Below we can see an example of invalid code:

    <?xml version="1.0" ?>
    <content>
      some content here
    </content>
    <content>
      error, the document cannot have another main tag!
    </content>

    To fix this file, we must pack everything with one more tag.

  3. The tag may contain the name and the attributes name="value". No other content is allowed. Every attribute must have a value defined.

  4. A document should have a prolog and DTD block (Document Type Definition).

  5. Special symbols like < or & must be encoded with entities.

XML does not put any limitations on the attribute value form and the meaning of the text between the tags. It depends on the concrete language needs and OPT makes use of this fact.

2. OPT parser configuration

The default OPT parser provides many options to increase or decrease the compatiblity with XML standard. In the most truncated version (so-called quirks mode) the parser looks for its own tags only, and the rest is treated as a static text, just like in PHP and Smarty. However, the default settings are chosen to provide the maximum possible compatibility with XML. OPT expects us to have a prolog in every template, properly closed tags and so on. Let's write a sample template:

<?xml version="1.0" ?>
<opt:root>
  <ul>
    <li>Element</li>
    <li>Element</li>
    <li>Element</li>
  </ul>
</opt:root>

After executing it and looking at the sources we could see that OPT displays everything in one line:

<ul> <li>Element</li> <li>Element</li> <li>Element</li> </ul>

It is not a bug. By default, OPT cuts the unnecessary white characters from the source code to reduce the output size and obfuscate the output code which is useful on production servers. But during the development we would like to keep the original form in order to make the analysis and debugging possible. In order to do this, we disable the configuration option stripWhitespaces and recompile the template:

$tpl = new Opt_Class;
// ...
$tpl->stripWhitespaces = false;

To force the template recompilation, we remove the cached compiled version from the directory pointed by the compileDir option. Let's take a look at the remaining options that affect XML syntax parsing:

prologRequired
If it is set to false, the templates do not have to have a prolog (it is treated as a text to display, but still validated).
singleRootNode
If it is set to false, OPT allows to have more main tags in a single template.
htmlAttributes
If it is enabled (true), OPT allows using a shortened attribute form without a value specified.
printComments
If it is enabled (true), the XML comments are sent to the browser, too.
unicodeNames
The XML standard accepts the Unicode in the tag names, but due to the performance reasons it is disabled in OPT by default. This option lets us to enable it.

In this article we assume that OPT works with the default settings.

3. Constructing the main template

Usually, the websites do not use complete, separate templates for each page but rather construct the output from a couple of smaller files. The footer and header remain the same across the pages, so they are loaded from a common file, where the module-specific content is embedded. While creating the main template in Open Power Template 2, we must pay attention to the XML prolog and DTD. If we type them directly, OPT keeps them for itself and does not display them in the output code. To simplify working with them, we may use the two instructions: opt:prolog and opt:dtd. Furthermore, we will make use of a "container" opt:root which provides a root node for the XML document and the rest of the content. Our template may look like this:

<?xml version="1.0" ?>
<opt:root>
<opt:prolog />
<opt:dtd template="xhtml10" />
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="pl" lang="pl">
  <head>
    <title>My website</title>
    <!-- remaining tags -->
  </head>
  <body>
    <!-- header layout -->
 
    <!-- beginning of the code for displaying modules -->
    <opt:section name="modules">
      <opt:include from="modules" />
    </opt:section>
    <!-- end of the code for displaying modules -->
 
    <!-- footer layout -->
  </body>
</html>
</opt:root>

The prolog visible at the beginning of the document is not sent to the browser, but parsed by OPT for internal purposes. The "visible" prolog is generated by opt:prolog instruction. It accepts the same arguments, as the original problog, but if we do not provide any, it uses the default values for them. Another simplification is opt:dtd instruction, where the DTD block is enclosed. We can write everything manually:

<opt:dtd><![CDATA[
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
]]></opt:dtd>

Manual DTD declaration requires it to be enclosed within a CDATA section. You do not have to worry about that, opt:dtd and OPT take care of it and do everything to make it correctly displayed for the browser.

However, this is not enough. As we already have an instruction, we get a couple of predefined DTD templates for the most common Document Type Definitions. They are selected with an template attribute and have simple, intuitive names such as xhtml10, html4 or html5.

After those two tags, we are allowed to put the opening <html> and start defining the website layout. By the way we should notice the way we display the module-dependent content. As we are supposed to know, in OPT we work with objects called 'views'. Each view is a template with the corresponding script data, so that each file has its own variable space and avoids naming collisions. The modules may generate their own view objects and pass them as their results to the controller:

public function action()
{
  $view = new Opt_View('module_index.tpl');
  $view->zmienna = 'foo';
 
  return $view;
} // end action();

Then they can be packed into a section and assigned to the main template. The opt:include instruction renders the view objects, and what is more - it can read them directly from a section. It is illustrated by the sample codes. Below we can see, how to obtain a view from a module and append it to a section.

$moduleViews = array();
foreach($this->currentModules as $module)
{
  $view = $module->action();
  if($view instanceof Opt_View)
  {
    $moduleViews[] = array('view' => $view);
  }
}
 
$layout = new Opt_View('layout.tpl');
$layout->modules = $moduleViews;
 
$output->render($layout);

Now we have both the main template and the PHP code to prepare it.

4. Constructing module templates

Let's take a look, how to create templates for website modules. In this case, we cannot render prologs and document type definitions, so our basic tool will be opt:root:

<?xml version="1.0" ?>
<opt:root>
  <h1>News</h1>
  <p>A news page</p>
  <opt:section name="news">
    ...
  </opt:section>
</opt:root>

Similarly to the previous chapter, the XML prolog we can see is not rendered but used for internal purposes by OPT (it does not mean that it may contain a nonsense - the compiler validates both syntax and the contents). Thanks to opt:root we can put more than one tag in the content. Moreover, this instruction allows to specify the HTML escaping rules for the template and load an extra template with snippets that can be used in our code:

<?xml version="1.0" ?>
<opt:root escaping="yes" include="snippets.tpl">
  <h1>News</h1>
  <p>A news page</p>
  <opt:section name="news">
    ...
  </opt:section>
 
  <!-- paste a default pagination snippet from snippets.tpl -->
  <opt:insert snippet="pagination" />
</opt:root>

Because of that, opt:root is worth having in every template, even if we disabled a certain compiler option for checking the root nodes.

5. Managing attributes

As we can create a basic template structure, we are ready to learn how to generate the attributes and their values dynamically. Everyone who used to write templates in PHP or Smarty should remember that writing a proper algorithm to display an attribute depending on certain conditions led to create an unreadable, messy code. In Open Power Template we do not suffer from that, but we must change the way we think about attributes. First of all, we remind that the template must be a correct XML document and we must not write something like this:

<div $class>
 
</div>

Moreover, we must not write both <div class="$variable"> and <div class="{$variable}">. Such mistakes are very common among the new OPT users who used to write in Smarty. Everything will become clear once you understand some simple rules. To clarify everything - every code like $variable, $a+$b or function($variable) is called expression. The curly brackets we can see in the static text

<p>{$zmienna}</p>

are not a part of expression, but a sign for compiler: 'right in this place you must display the result of evaluating this expression'. If we put an expression in an attribute, we do not write them, because now the expression is enclosed by the attribute quotes. By default, OPT prints the encountered value of an attribute: the code <div class="$variable"> gives us the output <div class="$variable"> because we have not informed the compiler that we expect a dynamic value here. In order to make such a notification, we must use the special namespace parse:

<div parse:class="$variable">
  ...
</div>

Now the compiler will parse it correctly and read the attribute value from a variable $variable. The parse namespace can be also used with some instruction attributes. Such cases are mentioned in the manual.

The way OPT handles dynamic attribute values causes some problems, if the attribute already belongs to a namespace. This problem will be fixed in OPT 2.1, where the new syntax will be introduced: <div class="parse:$variable">. You do not have to bother with syntax changes, as the new version will provide both the compatibility mode and the automatic template converter.

Dynamically calculated values may contain some HTML code that may potentially destroy our template structure. However, OPT pays attention to it and the attribute values are automatically escaped. By default, the same process is applied to the static text expressions, too. The HTML escaping process can be controlled at the compiler, template and expression level.

So far we know how to read an attribute value from an expression, but what to do, if the whole attribute must be displayed conditionally? For more advanced manipulations OPT provides another instruction: opt:attribute which creates an attribute with the specified name and value in the parent element. Below we can see a sample code that marks certain topic on a discussion board by adding an appropriate CSS class to it:

<opt:section name="topics">
<tr>
  <opt:attribute str:name="class" str:value="marked" opt:if="$topics.marked" />
 
  <td> ... </td>
  <td> ... </td>
  ...
</tr>
</opt:section>

opt:attribute accepts other OPT instructions in their attribute form, such as opt:if (conditional attribute displaying) or opt:section (loading a list of attributes from a section). By the way, we can observe what has been told a bit earlier. The extra namespaces can be used with certain instruction attributes. Here, the instruction expects the expression as the value of attributes name and value. However, in order not to write a code like name="'class'" with duplicated quotes, the compiler provides a more convenient form: the str: namespace which tells us: "you expect an expression here, but I want this expression to be a static text". Sometimes the situation is different - the compiler expects a string, but we want to load it from a variable. Then we use parse:.

But let's get back to our opt:instruction attribute. If we want to select the value conditionally, OPT provides us another trick:

<opt:section name="topics">
<tr>
  <opt:attribute str:name="class">
    <opt:value test="$topics.sticky">sticky</opt:value>
    <opt:value test="$topics.announcement">announcement</opt:value>
    <opt:value test="$topics.important">important</opt:value>
    <opt:value test="$topics.hot">hot</opt:value>
  </opt:attribute>
 
  <td> ... </td>
  <td> ... </td>
  ...
</tr>
</opt:section>

To define a default value, if neither of the conditions is passed, we either add an valueattribute to the instruction or put the opt:value tag without a condition defined.

In order to construct a dynamic attribute list from a container, we do not have to enclose opt:attribute within complex clauses with sections etc. All we need are two special attributes: opt:attributes-build and opt:attributes-ignore that load the attribute list from a container. The second one allows us to define the attribute names that needs to be ignored (an array or a string with attribute names separated with commas).

<textarea opt:attributes-build="$attributeList" opt:attributes-ignore="class, id">
  Text
</textarea>

The sample code above allows us to create an attribute list for the <textarea> tag. We are guaranteed that the programmers are not able to add any class and id attributes which could break the layout. Below, we can see a sample data for the template:

$view->attributeList = array(
  'rows' => 50,
  'cols' => 30,
  'class' => 'foo'  // this attribute will be ignored and skipped
);

There is one more useful trick. Sometimes we want to add a dynamic attribute to a single tag, but opt:attribute forces us to add some content to it. We must force OPT to ignore the white characters and print us a single tag inspite of what it sees in the code. This can be done in the following way:

<hr opt:single="yes">
  <opt:attribute str:name="class" str:value="foo" opt:if="$someCondition" />
</hr>

Thanks to opt:single our <hr> tag always remains printed as a single tag <hr /> with an optional class attribute.

As we can see, despite some advanced functionality and different approach, OPT tries to make the template code clear and readable.

6. Managing tags

Another issue we are going to discuss is tag management. Here, OPT provides some appropriate tools, too. The basic one is opt:tag instruction:

<opt:tag name="$tagName">
  Some content
</opt:tag>

It allows to create dynamic tag with a selected name. The $tagName variable value may contain a namespace in an XML notation, but it may be also set separately with ns attribute. The second way has an advantage. If the expression defining the namespace is empty (its value is null), OPT knows not to prepend a colon to the tag name. Sometimes we may want our tag to contain a name attribute in the output. We add it with opt:attribute instruction.

The second useful tool is opt:on attribute. It basically works much like opt:if, but does not hide entire content, but just the tag. The content is always displayed. It can be seen in the action in the following example:

<p class="author"><span>Author:</span> <a parse:href="$homepage" opt:on="$homepage">{$nickname}</a></p>

If the user does not set a home page address, we do not want to create a hyperlink around his nickname, but the nickname itself still must be printed. Depending on the $homepage state (set or not set), we will get one of the following outputs:

<!-- when $homepage is set -->
<p class="author"><span>Author:</span> <a href="http://www.example.com/">User</a></p>
 
<!-- when $homepage is not set -->
<p class="author"><span>Author:</span> User</p>

In OPT 2.1 opt:on will be renamed to opt:omit-tag for the sake of clarity. Of course thanks to the automatic converter and the compatibility mode you do not have to worry about this change very much.

7. Embedding JavaScript and CSS

Modern websites make a heavy use of JavaScript and CSS. An XML template engine must provide a proper support for them and Open Power Template is not an exception. Since now, our basic tool are CDATA sections. The XML standard says that the contents enclosed within a CDATA section are treated as a single block of static text and are not parsed. We are allowed to enclose XML tags there, break XML rules, use XML special symbols etc. without any problems. Open Power Template keeps the semantics of the CDATA sections and does not parse their contents. It is rewritten to the output together with the CDATA delimiters, so that browsers should also ignore its internal structure, at least theoretically.

Unfortunately, sometimes CDATA sections are not enough. Let's consider a following example, where we want to read a piece of JavaScript code from an OPT variable:

<script type="text/javascript">
<![CDATA[
    document.write('Test: {$variable} or ]]>{$variable}<![CDATA[?');
]]>
</script>

After executing this template we would get:

<script type="text/javascript">
<![CDATA[
    document.write('Test: {$variable} or ]]>variable value<![CDATA[?');
]]>
</script>

This is not exactly what we would like to get. The compiler must have broken the CDATA section in order to display a variable value, and then open it again. It does not look nice and moreover, many browsers could have problems with parsing it properly. A solution is provided by the opt:literal instruction which controls the CDATA section behaviour:

<script type="text/javascript">
<opt:literal type="comment_cdata">
<![CDATA[
    document.write('Test: {$variable} or ]]>{$variable}<![CDATA[?');
]]>
</opt:literal>
</script>

opt:literal makes the CDATA delimiters in the template not to be sent to the browser, although they are still parsed properly. What is more, the instruction allows to pack the content with one of four delimiter types selected with the type attribute:

  • cdata - the content is packed within <![CDATA[ and ]]> delimiters.
  • comment_cdata - the content is packed within /* <![CDATA[ */ and /* ]]> */.
  • comment - the content is packed within <!-- and --> (a dynamic comment).
  • transparent - does not pack the content within anything. It just hides the <![CDATA[ and ]]> delimiters in the content.

So, the final result of our template will be:

<script type="text/javascript">
/* <![CDATA[ */
    document.write('Test: {$variable} or variable value?');
/* ]]> */
</script>

The <![CDATA[ content is still not processed, but the delimiters are hidden, giving us a nice and correct JavaScript code. Moreover we have packed it within the delimiters that guarantee a proper JavaScript execution in both newer and older browsers.

The same trick can be used to create conditional Internet Exporer comments. We leave it as an exercise for you.

8. Final information

In the example codes we could have seen many different namespaces, with the primary one opt:. What if we use a tag that is not recognizable by OPT? To answer for that question, we must learn how OPT processes the XML namespaces. They are divided in two groups:

  • User namespaces - we do not check what they are, but just rewrite them to the output.
  • OPT namespaces - they contain OPT control stuff and we must process them.

The namespaces that belong to the first group, are displayed in the output code immediately. The second group is processed by OPT and their tags do not appear in the output. If OPT encounters an tag in an OPT namespace and is not able to recognize it (i.e. something like opt:foo), it ignores it together with the content. It makes finding mistakes a bit harder, but is necessary, because some instructions make a heavy use of such anonymous tags. What is more, the programmer may give them an extra meaning by creating a new instruction or component.

The OPT namespaces must be registered in the main library object. By default, there are three of them: opt, parse and str. To register a new one, we use the register() method:

$tpl = new Opt_Class;
// ...
$tpl->register(Opt_Class::OPT_NAMESPACE, 'foo');
// ...
$tpl->setup();

Now OPT treats the foo namespace as the internal namespace and we can add our own instructions, components and blocks to it.

9. Conclusion

Processing the HTML and XML code is one of the more important tasks for modern template engines. In Open Power Template it must look a bit different due to the way the library works, but fortunately it is still quite simple once we learn some basic rules. We hope that this article was useful for you and you found here a lot of helpful information about the tools provided by OPT.

Open Power Template 2.0 projects tutorial

This text is licensed under Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States license.

Questions and feedback

This is the end of this article. Please, leave a comment.