Smarty Forum Index Smarty
The discussions here are for Smarty, a template engine for the PHP programming language.
Dedicated server web hosting provided by Guru-host.eu.
Smarty XSLT block function

 
Post new topic   Reply to topic    Smarty Forum Index -> Smarty Development
View previous topic :: View next topic  
Author Message
hdkeeper
Smarty Rookie


Joined: 20 Nov 2008
Posts: 13

PostPosted: Thu Nov 20, 2008 8:02 pm    Post subject: Smarty XSLT block function Reply with quote

Here's an example of quick and dirty XSLT plugin for Smarty 2.6.20. It supports PHP arrays, classes and raw XML as data source. Support for different encodings is also present. This plugin requires PHP-5 with DOM & XSL extensions, and PEAR::XML_Serializer class. Sorry, no version for PHP-4 yet.

plugins/block.xslt.php
Code:
<?php
/**
 * Smarty plugin
 * @package Smarty
 * @subpackage plugins
 */

/**
 * Smarty {xslt}{/xslt} block plugin
 *
 * Type:     block function<br>
 * Name:     xslt<br>
 * Purpose:  Apply recursive transformations to complex data structures like
 *           nested array, classes, trees etc. Usage for simple data structures
 *           will end with great pain in the ass!<br>
 * Requires: PHP-5, DOM and XSL extensions, PEAR::XML_Serializer<br>
 *
 * @author Keeper <hd_keeper at mail dot ru>
 * @param array $params
 * <pre>
 * Params:  from: mixed - array or class containing source data to display,
 *                        not used if 'from_xml' was provided
 *          from_xml: string - XML containing source data to display
 *          assign_xml: string - template variable the intermediate XML will be
 *                               assigned to (useful for debugging)
 *          encoding: string - character set to use (UTF-8 by default)
 *          defaultTagName: string - default name for tags (see XML_Serializer)
 *          rootName: string - name of the root tag (see XML_Serializer)
 *
 * Any other parameters will be passed as options to XML_Serializer
 * You should provide either 'from' or 'fromxml' parameter!
 * </pre>
 * @param string $content - XSLT template to apply
 * @param Smarty $smarty - clever simulation of a method
 * @return string - generated HTML/XML data
 */

function smarty_block_xslt( $params, $content, &$smarty)
{
   if (is_null( $content)) {
      return;
   }

   // Setup default parameters
   $from = null;
   $from_xml = null;
   $assign_xml = null;
   $encoding = null;
   $xml_serializer_options = array(
      'addDecl' => true,
      'classAsTagName' => true,
   );

   // Obtain provided parameters
   foreach ($params as $_key => $_val) {
      switch ($_key) {
         case 'from':
         case 'from_xml':
         case 'assign_xml':
            $$_key = $_val;
            break;

         case 'encoding':
            $$_key = $xml_serializer_options[ $_key] = $_val;
            break;

         default:
            $xml_serializer_options[ $_key] = $_val;
            break;
      }
   }
   
   // Validate provided parameters
   if (is_null( $from) && is_null( $from_xml)) {
      $smarty->trigger_error( "xslt: either 'from' or 'fromxml' attribute should be provided", E_USER_ERROR);
   }
   
   // Format intermediate XML-data if not provided
   if (is_null( $from_xml)) {
      require_once 'XML/Serializer.php';
      $serializer = new XML_Serializer( $xml_serializer_options);
      $serializer->serialize( $from);
      $from_xml = $serializer->getSerializedData();
      if (!is_null( $assign_xml)) {
         $smarty->assign( $assign_xml, $from_xml);
      }
   }
   
   // Load XML-data and XSLT-template into DOM documents
   $dom_data = new DOMDocument( '1.0', $encoding);
   $dom_data->loadXML( $from_xml);
   $dom_template = new DOMDocument( '1.0', $encoding);
   $dom_template->loadXML( $content);

   // Transform XML-data to format the output XML/HTML
   $_output = '';
   $processor = new XSLTProcessor();
   $processor->importStyleSheet( $dom_template);
   $_output = $processor->transformToXML( $dom_data);

   return $_output;
}
?>
Back to top
View user's profile Send private message
hdkeeper
Smarty Rookie


Joined: 20 Nov 2008
Posts: 13

PostPosted: Thu Nov 20, 2008 8:14 pm    Post subject: Reply with quote

Now some stuff to test this plugin.

test.php
Code:
<?php
require_once 'Smarty/Smarty.class.php';

$smarty = new Smarty();
$smarty->template_dir = $_SERVER['DOCUMENT_ROOT'].'/';
$smarty->compile_dir = $_SERVER['DOCUMENT_ROOT'].'/templates_c/';
$smarty->cache_dir = $_SERVER['DOCUMENT_ROOT'].'/cache/';

// Use arrays as data source

$list = array(
   array(
      'id'   => rand( 0, 1000),
      'name'   => 'File',
      'child_list' => array(
         array(
            'id'   => rand( 0, 1000),
            'name'   => 'Open',
            'child_list' => array(),
         ),
         array(
            'id'   => rand( 0, 1000),
            'name'   => 'Save',
            'child_list' => array(),
         ),
         array(
            'id'   => rand( 0, 1000),
            'name'   => 'Close',
            'child_list' => array(),
         ),
         array(
            'id'   => rand( 0, 1000),
            'name'   => 'Exit',
            'child_list' => array(),
         )
      )
   ),
   array(
      'id'   => rand( 0, 1000),
      'name'   => 'Edit',
      'child_list' => array(
         array(
            'id'   => rand( 0, 1000),
            'name'   => 'Cut',
            'child_list' => array(),
         ),
         array(
            'id'   => rand( 0, 1000),
            'name'   => 'Copy',
            'child_list' => array(),
         ),
         array(
            'id'   => rand( 0, 1000),
            'name'   => 'Paste',
            'child_list' => array(),
         ),
         array(
            'id'   => rand( 0, 1000),
            'name'   => 'Delete',
            'child_list' => array(),
         )
      )
   ),
   array(
      'id'   => rand( 0, 1000),
      'name'   => 'View',
      'child_list' => array()
   )
);

// Use classes as data source
 
class EmptyClass {}

class Node
{
   public $id;      // integer identifier
   public $name;   // Name of this node
   public $child_list;   // array of Tree or Node class

   function __construct( $name = '') {
      $this->id = rand( 0, 1000);
      $this->name = $name;
      $this->child_list = array();
   }

   function __destruct() {
      foreach ($this->child_list as $child)
         unset( $child);
   }

   function addChild( &$child) {
      $this->child_list[] = $child;
   }
} // class Node

class Tree extends Node {}

$tree = new Tree('Menu');
$node = array();
foreach (array('File','Edit','View') as $name)
   $tree->addChild( $node[$name] = new Node($name));
foreach (array('Open','Save','Close','Exit') as $name)
   $node['File']->addChild( new Node($name));
foreach (array('Cut','Copy','Paste','Delete') as $name)
   $node['Edit']->addChild( new Node($name));
   
// Use XML as data source

$xml = '<Tree>
   <id>0</id>
   <name>Menu</name>
   <child_list>
      <Node>
         <id>1</id>
         <name>File</name>
         <child_list>
            <Node>
               <id>11</id>
               <name>Open</name>
            </Node>
            <Node>
               <id>12</id>
               <name>Save</name>
            </Node>
            <Node>
               <id>13</id>
               <name>Close</name>
            </Node>
            <Node>
               <id>14</id>
               <name>Exit</name>
            </Node>
         </child_list>
      </Node>
      <Node>
         <id>2</id>
         <name>Edit</name>
         <child_list>
            <Node>
               <id>21</id>
               <name>Cut</name>
            </Node>
            <Node>
               <id>22</id>
               <name>Copy</name>
            </Node>
            <Node>
               <id>23</id>
               <name>Paste</name>
            </Node>
            <Node>
               <id>24</id>
               <name>Delete</name>
            </Node>
         </child_list>
      </Node>
      <Node>
         <id>3</id>
         <name>View</name>
      </Node>
   </child_list>
</Tree>';

// Assign all data and display

$smarty->assign( 'ARRAY_DATA', $list);
$smarty->assign( 'CLASS_DATA', $tree);
$smarty->assign( 'XML_DATA',   $xml);
$smarty->display( 'test.tpl');

?>
Back to top
View user's profile Send private message
hdkeeper
Smarty Rookie


Joined: 20 Nov 2008
Posts: 13

PostPosted: Thu Nov 20, 2008 8:20 pm    Post subject: Reply with quote

Damn, I cannot post HTML/XML code yet... -_-
Back to top
View user's profile Send private message
hdkeeper
Smarty Rookie


Joined: 20 Nov 2008
Posts: 13

PostPosted: Thu Nov 20, 2008 8:21 pm    Post subject: Reply with quote

Smarty template with XSLT included.

test.tpl
Code:
<html>
<head>
<title>Smarty XSLT block plugin</title>
</head>
<body>

<h1>Smarty XSLT block plugin</h1>


<h2>Using data from arrays</h2>

{xslt from=$ARRAY_DATA defaultTagName="Node" rootName="child_list" assign_xml="DEBUG_XML_ARRAY"}
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="html" encoding="KOI8-R" indent="yes"/>
   <xsl:template match="child_list">
      <ul>
         <xsl:for-each select="Node">
            <li>
               <a>
                  <xsl:attribute name="href">
                     <xsl:value-of select="id"/>
                  </xsl:attribute>
                  <xsl:value-of select="name"/>
               </a>
               <xsl:apply-templates select="child_list[ node()]"/>
            </li>
         </xsl:for-each>
      </ul>
    </xsl:template>
</xsl:stylesheet>
{/xslt}

<h3>Intermediate XML</h3>

<pre>{$DEBUG_XML_ARRAY|escape}</pre>



<h2>Using data from classes</h2>

{xslt from=$CLASS_DATA assign_xml="DEBUG_XML_CLASS"}
{include file="test.xsl"}
{/xslt}

<h3>Intermediate XML</h3>

<pre>{$DEBUG_XML_CLASS|escape}</pre>



<h2>Using data from XML text</h2>

{xslt from_xml=$XML_DATA}
{include file="test.xsl"}
{/xslt}

<h3>Source XML</h3>

<pre>{$XML_DATA|escape}</pre>

</body>
</html>
Back to top
View user's profile Send private message
hdkeeper
Smarty Rookie


Joined: 20 Nov 2008
Posts: 13

PostPosted: Thu Nov 20, 2008 8:22 pm    Post subject: Reply with quote

Standalone XSL template referred from above.

test.xsl
Code:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="html" encoding="KOI8-R" indent="yes"/>

   <xsl:template match="/">
      <xsl:for-each select="Tree">
         <xsl:apply-templates select="child_list"/>
      </xsl:for-each>
   </xsl:template>
   
   <xsl:template match="child_list">
      <ul>
         <xsl:for-each select="Node">
            <li>
               <a>
                  <xsl:attribute name="href">
                     <xsl:value-of select="id"/>
                  </xsl:attribute>
                  <xsl:value-of select="name"/>
               </a>
               <xsl:apply-templates select="child_list[ node()]"/>
            </li>
         </xsl:for-each>
      </ul>
   </xsl:template>

</xsl:stylesheet>
Back to top
View user's profile Send private message
hdkeeper
Smarty Rookie


Joined: 20 Nov 2008
Posts: 13

PostPosted: Sun Nov 23, 2008 12:15 pm    Post subject: Reply with quote

The next version of XSLT plugin for your consideration. No PEAR::XML_Serializer class is longer required, and some speedup is also gained. This version requires PHP-5 with mbstring, DOM & XSL extensions.

plugins/block.xslt.php
Code:
<?php
/**
 * Smarty plugin
 * @package Smarty
 * @subpackage plugins
 */

/**
 * Smarty {xslt}{/xslt} block plugin
 *
 * Type:     block function<br>
 * Name:     xslt<br>
 * Purpose:  Apply recursive transformations to complex data structures like
 *           nested array, classes, trees etc. Usage for simple data structures
 *           will end with great pain in the ass!<br>
 * Requires: PHP-5 with MBString, DOM, XSL extensions<br>
 *
 * @author Keeper <hd_keeper at mail dot ru>
 * @param array $params
 * <pre>
 * Params:  from: mixed - array or class containing source data to display,
 *                        ignored if 'from_xml' was provided
 *          from_xml: string - XML containing source data to display
 *          assign_xml: string - template variable the intermediate XML will be
 *                               assigned to (useful for debugging)
 *          root_tag: string - name for the root element (default "root")
 *          default_tag: string - default name for tags (default "data")
 *          key_attribute: string - attribute name for array keys (default none)
 *          encoding: string - character set to use (default "UTF-8")
 *
 * You should provide either 'from' or 'from_xml' parameter!
 * </pre>
 * @param string $content - XSL-template to apply
 * @param Smarty $smarty - clever simulation of a method
 * @return string - generated HTML/XML data
 */

function smarty_block_xslt( $params, $content, &$smarty)
{
   if (is_null( $content)) {
      return;
   }

   // Setup default parameters
   $from = null;
   $from_xml = null;
   $assign_xml = null;
   $opts = array(
      'root_tag' => "root",
      'default_tag' => "data",
      'key_attribute' => null,
      'encoding' => "UTF-8"
   );

   // Obtain provided parameters
   foreach ($params as $_key => $_val) {
      switch ($_key) {
         case 'from':
         case 'from_xml':
         case 'assign_xml':
            $$_key = $_val;
            break;

         case 'root_tag':
         case 'default_tag':
         case 'key_attribute':
         case 'encoding':
            $opts[ $_key] = $_val;
            break;

         default:
            $smarty->trigger_error( "xslt: unknown attribute '$_key'");
      }
   }
   
   // Create DOM ducument containing provided data
   $dom_data = new DOMDocument( '1.0', $opts['encoding']);
   if (!is_null( $from_xml)) {
      // Load provided raw XML-data into DOM document
      $dom_data->loadXML( $from_xml);
   } elseif (!is_null( $from)) {
      // Build DOM document from provided source data
      mb_regex_encoding( $opts['encoding']);
      smarty_block_xslt_append( $dom_data, $opts['root_tag'], $from, $opts);
   } else {
      $smarty->trigger_error( "xslt: either 'from' or 'fromxml' attribute should be provided", E_USER_ERROR);
   }
   
   // Assign intermediate XML-data to the template variable
   if (!is_null( $assign_xml)) {
      $from_xml = $dom_data->saveXML();
      $smarty->assign( $assign_xml, $from_xml);
   }
   
   // Create DOM document containing provided XSL-template
   $dom_template = new DOMDocument( '1.0', $opts['encoding']);
   $dom_template->loadXML( $content);

   // Transform XML-data to make an output XML/HTML
   $_output = "";
   $processor = new XSLTProcessor();
   $processor->importStyleSheet( $dom_template);
   $_output = $processor->transformToXML( $dom_data);

   return $_output;
}

/**
 * Recursively walk through the provided variable,
 * append all found data to the DOM document
 *
 * @access private
 * @param DOMNode $dom_node - XML-element the childs are to be appended to
 * @param string  $use_key - suggested key to be used as tag name
 * @param mixed   $data - variable to be stored in the DOM document
 * @param array   $opts - set of immutable options
 * @return void
 */
function smarty_block_xslt_append( DOMNode &$dom_node, $use_key, &$data, &$opts)
{
   // For objects, use class name as tags
   $use_tag = is_object($data) ? get_class( $data) : $use_key;
   // Use default tag name instead of invalid tag names
   if (!mb_ereg_match( '^[[:alpha:]_][[:alnum:]:_\.\-]*$', $use_tag))
      $use_tag = $opts['default_tag'];
   if  (is_array($data) || is_object($data)) {
      // Add a node for entire array or object
      $dom_child = $dom_node->appendChild( new DOMElement(
         smarty_block_xslt_conv( $use_tag, $opts)
      ));
      // Enumerate array elements or object properties
      foreach ($data as $_key => $_val) {
         // Recurrent call for array element or object property
         smarty_block_xslt_append( $dom_child, $_key, $_val, $opts);
      }
   } else {
      // Plain data is added as leaf node
      $dom_child = $dom_node->appendChild( new DOMElement(
         smarty_block_xslt_conv( $use_tag, $opts),
         htmlspecialchars( smarty_block_xslt_conv( $data, $opts), ENT_COMPAT, "UTF-8")
      ));
   }
   // Store key as attribute if required
   if (!empty( $opts['key_attribute'])) {
      $dom_child->setAttribute(
         smarty_block_xslt_conv( $opts['key_attribute'], $opts),
         smarty_block_xslt_conv( $use_key, $opts)
      );
   }
}

/**
 * Convert data string to internal encoding of DOM document (UTF-8)
 *
 * @access private
 * @param string $str - data to be converted
 * @param array  $opts - set of immutable options
 * @return string - data converted to internal encoding
 */
function smarty_block_xslt_conv( $str, &$opts)
{
   if (!mb_check_encoding( (string) $str, $opts['encoding']))
      trigger_error( "xslt: data string '$str' has invalid encoding", E_USER_WARNING);
   return mb_convert_encoding( (string) $str, "UTF-8", $opts['encoding']);
}
?>


Last edited by hdkeeper on Thu Dec 25, 2008 10:31 am; edited 4 times in total
Back to top
View user's profile Send private message
hdkeeper
Smarty Rookie


Joined: 20 Nov 2008
Posts: 13

PostPosted: Sun Nov 23, 2008 12:18 pm    Post subject: Reply with quote

As names of parameters for the plugin were changed, we have to change our testing template, too.

test.tpl
Code:
<html>
<head>
<title>Smarty XSLT block plugin</title>
</head>
<body>

<h1>Smarty XSLT block plugin</h1>


<h2>Using data from arrays</h2>

{xslt from=$ARRAY_DATA root_tag="child_list" default_tag="Node" assign_xml="DEBUG_XML_ARRAY"}
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="html" encoding="KOI8-R" indent="yes"/>
   <xsl:template match="child_list">
      <ul>
         <xsl:for-each select="Node">
            <li>
               <a>
                  <xsl:attribute name="href">
                     <xsl:value-of select="id"/>
                  </xsl:attribute>
                  <xsl:value-of select="name"/>
               </a>
               <xsl:apply-templates select="child_list[ node()]"/>
            </li>
         </xsl:for-each>
      </ul>
    </xsl:template>
</xsl:stylesheet>
{/xslt}

<h3>Intermediate XML</h3>

<pre>{$DEBUG_XML_ARRAY|escape}</pre>



<h2>Using data from classes</h2>

{xslt from=$CLASS_DATA assign_xml="DEBUG_XML_CLASS"}
{include file="test.xsl"}
{/xslt}

<h3>Intermediate XML</h3>

<pre>{$DEBUG_XML_CLASS|escape}</pre>



<h2>Using data from XML text</h2>

{xslt from_xml=$XML_DATA}
{include file="test.xsl"}
{/xslt}

<h3>Source XML</h3>

<pre>{$XML_DATA|escape}</pre>

</body>
</html>
Back to top
View user's profile Send private message
hdkeeper
Smarty Rookie


Joined: 20 Nov 2008
Posts: 13

PostPosted: Thu Dec 25, 2008 10:30 am    Post subject: Reply with quote

Some bugfixes were applied to plugins/block.xslt.php -- see above.
Back to top
View user's profile Send private message
kannan
Smarty n00b


Joined: 27 Feb 2009
Posts: 1

PostPosted: Fri Feb 27, 2009 4:49 pm    Post subject: Smarty with xml Reply with quote

Can anyone please help me in writing a simple smarty code with an xml code as the template and the template xml code having a corresponding xslt.I am a beginner
Back to top
View user's profile Send private message
hdkeeper
Smarty Rookie


Joined: 20 Nov 2008
Posts: 13

PostPosted: Tue Mar 31, 2009 4:27 pm    Post subject: Reply with quote

Well... There were some simple samples of XML/XSLT code above, and examples of Smarty plugin invocation, too.

What goal you are going to achieve? What your code should display?
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    Smarty Forum Index -> Smarty Development All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group
Protected by Anti-Spam ACP