DOM API No Modification Allowed Error

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Post by Kieran Huggins »

I'd be interested to see your example!
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post 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.
thsoft
Forum Newbie
Posts: 9
Joined: Tue Jan 23, 2007 4:47 pm

XSLT rulez

Post 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!
thsoft
Forum Newbie
Posts: 9
Joined: Tue Jan 23, 2007 4:47 pm

xsl:copy-of and returning DocumentFragment

Post 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?
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post 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)
User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Post 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!
Post Reply