Page 2 of 2

Posted: Tue Jan 30, 2007 6:39 pm
by Kieran Huggins
I'd be interested to see your example!

Posted: Tue Jan 30, 2007 8:26 pm
by Ambush Commander
It's a bit bulky, but most of the code is to give the simulation of working with templates. You can cut down quite a bit of it if buy into the XSLT paradigm.

Code: Select all

<?php

interface Template {
    /**
     * Assigns a value that is available to the template
     * @param $key String key value will be retrieved by. May be restricted
     *             by the implementation.
     * @param $value Mixed value that will be retrieved. Accepted types
     *               may be restricted by implementation.
     */
    public function assign($key, $value);
    
    /**
     * Displays the template based on a style file
     * @param $style_file String filename of style file to use. Valid file
     *                    types restricted by implementation.
     */
    public function display($style_file);
}

class Template_XSLT implements Template
{
    
    protected $data;
    protected $root;
    
    public function __construct() {
        // set up data DOM we'll pass to XSLT
        $this->data = new DOMDocument();
        $this->root = $this->data->createElement('root');
        $this->data->appendChild($this->root);
    }
    
    /**
     * @param $style_file String filename of valid XSL transformation style
     */
    public function display($style_file) {
        $style = new DOMDocument();
        $style->load($style_file);
        $processor = new XSLTProcessor();
        $processor->registerPHPFunctions(); // so we can use 'php:function'
        $processor->importStylesheet($style);
        $html = $processor->transformToXML($this->data);
        echo $html;
    }
    
    /**
     * @param $key String key that is a valid XML element name
     * @param $value String, boolean, integer, float, or array value,
     *               objects not supported (yet)
     * @param $raw Does the data have raw character entities?
     */
    public function assign($key, $value, $raw = false) {
        $node = $this->domify($key, $value);
        $this->root->appendChild($node);
    }
    
    /**
     * Turns a mixed value into a set of nodes
     * @param $key Name of parent node to return
     * @param $value Value to convert, see assign for valid values
     * @param $raw Does the data have raw character entities?
     */
    protected function domify($key, $value, $raw = false) {
        if (is_array($value)) {
            $keys = array_keys($value);
            $node = $this->data->createElement($key);
            if ($keys === array_keys($keys)) {
                // list
                foreach ($value as $mvalue) {
                    $node->appendChild($this->domify('item', $mvalue, $raw));
                }
            } else {
                // map
                foreach ($value as $mkey => $mvalue) {
                    $node->appendChild($this->domify($mkey,  $mvalue, $raw));
                }
            }
            return $node;
        }
        // scalar
        if (is_bool($value)) $value = $value ? 'true' : 'false';
        if (!$raw) $value = htmlspecialchars($value);
        return $this->data->createElement($key, $value);
    }
    
}

// ripped off Smarty: <http://smarty.php.net/crashcourse.php>

// create object
$template = new Template_XSLT();

// assign some content. This would typically come from
// a database or other source, but we'll use static
// values for the purpose of this example.
$template->assign('name', 'george smith');
$template->assign('address', '45th & Harris');
$template->assign('logged-in', false);
$template->assign('favorite-colors', array('red', 'green', 'maroon'));

// display it
$template->display('index.xsl');

?>
And the XSL:

Code: Select all

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
  xmlns     = "http://www.w3.org/1999/xhtml"
  xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
  xmlns:php = "http://php.net/xsl">
<xsl:output method="html" />
<xsl:template match="/root">

<html>
<head>
    <title>User Info</title>
</head>
<body>

<h1>User Information</h1>

<ul>
    <li>Name: <xsl:value-of select="php:function('ucwords',string(name))" /></li>
    <li>Addr: <xsl:value-of select="address" /></li>
    <li>Logged in:
        <!-- if else functionality -->
        <xsl:choose>
            <xsl:when test="logged-in='true'">Yes</xsl:when>
            <xsl:otherwise>No</xsl:otherwise>
        </xsl:choose>
    </li>
</ul>

<h2>Favorite Colors</h2>

<ul>
    <!-- looping functionality -->
    <xsl:for-each select="favorite-colors/item">
        <li style="color:{.};"><!-- assigning to attributes functionality -->
            <xsl:value-of select="php:function('ucfirst',string(.))" />
        </li>
    </xsl:for-each>
</ul>

<p>Date retrieved: <xsl:value-of select="php:function('date','Y-m-d')" /></p>

</body>
</html>

</xsl:template>
</xsl:stylesheet>
Should work on any reasonably well-equipped PHP 5 installation.

XSLT rulez

Posted: Wed Jan 31, 2007 6:58 am
by thsoft
Hmm, you are absolutely right. I was familiar with XSLT before, but not aware of such capabilities about linking with PHP. But now I see that it's very powerful! Of course, this way, I have total control over XML declaration, doctype declaration etc. So many thanks!

xsl:copy-of and returning DocumentFragment

Posted: Thu Feb 01, 2007 8:17 am
by thsoft
Now, I'm using

Code: Select all

<xsl:copy-of select="php:function('Foo::bar')"/>
to insert DOM nodes returned from PHP functions. But it only works with elements and documents, but not with document fragments, which I actually return! (Neither with node lists...) Nothing is inserted in the result tree. And this in spite of that http://www.w3.org/TR/xslt#element-copy-of clearly states: "When the result of evaluating the expression is a result tree fragment, the complete fragment is copied into the result tree. When the result is a node-set, all the nodes in the set are copied in document order into the result tree". Did anyone succeed to pass document fragments to the XSLT processor?

Posted: Thu Feb 01, 2007 9:09 pm
by Ambush Commander
Hmm... there's no way you can insert the document fragment into the source XML file? (DOM offers some specialized functions to manage this)

Posted: Fri Feb 02, 2007 2:26 am
by Kieran Huggins
I do exactly this to load cached xml chunks, it may come in handy:

Code: Select all

// load the cached file (saves a biatch-load of time)
if(file_exists('path/to/cached_chunk.xml')){ // if there's a cache of the chunk
	$tempdoc->load('path/to/cached_chunk.xml'); // load the xml file into a second DOMDocument
	$node_to_import = $tempdoc->getElementsByTagName('*')->item(0); // selects first node in the document
	$imported_node = $doc->importNode($node_to_import,TRUE); // import into the working DOMDocument
	$doc->root->replaceChild($imported_node,$this); // replace the dummy node with the imported node
	return true; // avoid generating the chunk since I've loaded the cache instead
}
//...generate the chunk here
@Ambush Commander: thanks for the nice XSLT example!