<?php
class DomQuery {
protected $xpath;
function __construct($document) {
$this->xpath = new DomXPath($document instanceOf DomDocument ? $document : $document->ownerDocument);
}
function getFirstElementByClassname($class_name, $node = null) {
if (!$class_name) {
return $node;
}
if ($node) {
return $this->xpath->query("descendant::*[contains(@class,'$class_name')]", $node)->item(0);
}
return $this->xpath->query("descendant::*[contains(@class,'$class_name')]")->item(0);
}
}
class Domling {
protected $node;
protected $query;
protected
$captures = array();
function __construct($source) {
$this->node = $this->parse($source);
} else {
$this->node = $source;
}
$this->query = new DomQuery($this->node);
}
protected function parse($html) {
$document = new DomDocument();
$document->loadHtml("<html><body><div>" . $html . "</div></body></html>");
return $document->getElementsByTagName("body")->item(0)->firstChild;
}
$document->loadHtml($html);
return $document;
}
function purge() {
foreach ($this->captures as $capture) {
$capture->purge();
}
}
function render() {
$this->purge();
if ($this->node instanceOf DOMElement) {
preg_match('~<html>\s*<body>\s*<div>([\s\S]*)</div>\s*</body>\s*</html>\s*$~', $this->node->ownerDocument->saveHtml(), $matches);
return $matches[1];
}
return $this->node->saveHtml();
}
function capture($class_name) {
if (!isset($this->captures[$class_name])) {
$this->captures[$class_name] = new Capture($this->query->getFirstElementByClassname($class_name));
}
return $this->captures[$class_name];
}
function sequence($item_class_name, $container_class_name) {
if (!isset($this->captures[$item_class_name.":".$container_class_name])) {
$container = $this->query->getFirstElementByClassname($container_class_name);
$this->captures[$item_class_name.":".$container_class_name] = new Sequence(
$this->query->getFirstElementByClassname($item_class_name, $container),
$container);
}
return $this->captures[$item_class_name.":".$container_class_name];
}
}
class Capture extends Domling {
protected $placeholder_node;
function __construct(DomElement $node) {
parent::__construct($node);
$this->placeholder_node = $this->node->ownerDocument->createComment("placeholder");
$this->node->parentNode->replaceChild($this->placeholder_node, $this->node);
}
function purge() {
parent::purge();
if ($this->placeholder_node->parentNode) {
$this->placeholder_node->parentNode->removeChild($this->placeholder_node);
}
}
function bind
($data = array()) {
$clone = $this->node->cloneNode(true);
$this->placeholder_node->parentNode->insertBefore($clone, $this->placeholder_node);
$data = array('' => $data);
}
foreach ($data as $id => $value) {
$class_name = $matches[1];
$attribute = $matches[2];
$element = $this->query->getFirstElementByClassname($class_name, $clone);
$element->setAttribute($attribute, $value);
} else {
$element = $this->query->getFirstElementByClassname($id, $clone);
while ($element->hasChildNodes()) {
$element->removeChild($element->firstChild);
}
$element->appendChild($element->ownerDocument->createTextNode($value));
}
}
return $clone;
}
}
class Sequence extends Capture {
protected $container;
protected $placeholder_container;
function __construct(DomElement $node, DomElement $container) {
parent::__construct($node);
$this->container = $container;
$this->placeholder_container = $container->ownerDocument->createComment("container");
$this->container->parentNode->replaceChild($this->placeholder_container, $this->container);
}
function purge() {
parent::purge();
if ($this->placeholder_container->parentNode) {
$this->placeholder_container->parentNode->removeChild($this->placeholder_container);
}
}
function bind
($data = array()) {
if (!$this->container->parentNode) {
$this->placeholder_container->parentNode->replaceChild($this->container, $this->placeholder_container);
}
return parent::bind($data);
}
}
if (realpath($_SERVER['SCRIPT_NAME']) == __FILE__) {
// simple variable binding
$t = new Domling('<p class="hello"></p>');
$t->capture('hello')->bind("Hello World");
echo $t->render();
// <p class="hello">Hello World</p>
echo "\n---\n";
// Switching a block out
$t = new Domling('<p>Lorem Ipsum</p><p class="message">Hidden message</p>');
$t->capture('message');
echo $t->render();
// <p>Lorem Ipsum</p>
echo "\n---\n";
// Putting it back in
$t = new Domling('<p>Lorem Ipsum</p><p class="message">Hidden message</p>');
$block = $t->capture('message');
$block->bind();
echo $t->render();
// <p>Lorem Ipsum</p>
// <p class="message">Hidden message</p>
echo "\n---\n";
// And looping over a block
$t = new Domling('<ul class="links"><li class="link"><a class="anchor" href="#">title</a></li></ul>');
'Sitepoint' => 'http://www.sitepoint.com',
'Example' => 'http://www.example.org?foo=bar&ding=dong');
foreach ($links as $title => $link) {
$t->sequence('link', 'links')->bind(array('anchor:href' => $link, 'anchor' => $title));
}
echo $t->render();
// <ul class="links">
// <li class="link"><a class="anchor" href="http://www.sitepoint.com">Sitepoint</a></li>
// <li class="link"><a class="anchor" href="http://www.example.org?foo=bar&ding=dong">Example</a></li>
// </ul>
echo "\n---\n";
// And a full sample
$template = "
<h1 class='title'>title</h1>
<ul class='links'>
<li class='link'><a class='anchor' href='#'>title</a></li>
</ul>
<p class='switch'>Hide this</p>
";
$t = new Domling($template);
// let's bind some data to the template
$t->capture('title')->bind("Hello World");
// Or we can repeat a block as a sequence
'Sitepoint' => 'http://www.sitepoint.com',
'Example' => 'http://www.example.org?foo=bar&ding=dong');
foreach ($links as $title => $link) {
$t->sequence('link', 'links')->bind(array('anchor:href' => $link, 'anchor' => $title));
}
// we can even remove a block entirely
$t->capture('switch');
echo $t->render();
// <h1 class="title">Hello World</h1>
// <ul class="links">
// <li class="link"><a class="anchor" href="http://www.sitepoint.com">Sitepoint</a></li>
// <li class="link"><a class="anchor" href="http://www.example.org?foo=bar&ding=dong">Example</a></li>
// </ul>
}