Skip to content

Instantly share code, notes, and snippets.

@rintaun
Created June 6, 2012 18:23
Show Gist options
  • Save rintaun/2883727 to your computer and use it in GitHub Desktop.
Save rintaun/2883727 to your computer and use it in GitHub Desktop.
<?php
namespace Haml;
class Haml
{
public static function getTree($data)
{
$root = $last_node = new Node('', -1);
$re = '/^[\r\n]*([ \t]*)([^ \t]?(?:(?=.*?{[^}]*)\s*[^}]*}.*?|' .
'(?:(?=.*?\s*\|\s*)(?:.*?\s*\|\s*)*|.*?)))(\s*?)[\r\n]*$/m';
$indent_char = '';
$not_indent = '';
$indent_amt = 0;
$line = 0;
while (!empty($data)) {
$line++;
if (!preg_match($re, $data, $matches)) {
throw new Exception(sprintf(
'Parse error on line %d: ' .
'Unknown error',
$line
));
}
$data = \substr($data, \strlen($matches[0]) + \strlen($matches[4]));
$matches[0] = ltrim($matches[2], "\r\n");
if (empty($matches[0])) {
continue;
}
$indent = $matches[1];
if (count($root) == 0 && \strlen($indent) > 0) {
throw new Exception(sprintf(
'Syntax error on line %d: ' .
'Indenting at the beginning of the document is illegal.',
$line
));
}
if (empty($indent_char)) {
$indent_char = substr($indent, 0, 1);
$indent_amt = strlen($indent);
$not_indent = ($indent_char === ' ' ? "\t" : ' ');
} else if (strpos($indent, $not_indent) !== false) {
throw new Exception(sprintf(
'Syntax error on line %d: ' .
'Indentation can\'t use both tabs and spaces.',
$line
));
} else if (!is_int(strlen($indent) / $indent_amt)) {
throw new Exception(sprintf(
'Syntax error on line %d: ' .
'Inconsistent indentation: ' .
'%d %s%s %s used for indentation, but the rest ' .
'of the document was indented using %d %s%s',
$line,
strlen($indent),
substr($indent, 0, 1) === ' ' ? 'space' : 'tab',
strlen($indent) != 1 ? 's' : '',
strlen($indent) != 1 ? 'were' : 'was',
$indent_amt,
$indent_char === ' ' ? 'space' : 'tab',
$indent_amt != 1 ? 's' : ''
));
} else {
$level = strlen($indent);
$last_level = $last_node->getIndent() * $indent_amt;
$expected = $indent_amt + $last_level;
if ($level > $expected) {
throw new Exception(sprintf(
'Syntax error on line %d: ' .
'The line was indented %d levels deeper ' .
'than the previous line.',
$line,
($level - $last_level) / $indent_amt
));
}
}
$indent = \strlen($indent);
if ($indent_amt > 0) {
$indent = $indent / $indent_amt;
}
$node = new Node(trim($matches[2]), $indent);
while (!$node->hasParent()) {
if ($node->getIndent() > $last_node->getIndent() || !$last_node->hasParent()) {
if (substr($last_node->getContent(), 0, 3) === '!!!') {
throw new Exception(sprintf(
'Syntax error on line %d: ' .
'Illegal nesting: ' .
'nesting within a header command is illegal.',
$line
));
}
$last_node->addChild($node);
$last_node = $node;
} else {
$last_node = $last_node->getParent();
}
}
$line += preg_match_all('/(\r\n?|\n)/', $matches[0]);
$last_node = $node;
}
return $root;
}
public static function render($data)
{
$tree = static::getTree($data);
$iterator = new RecursiveIteratorIterator($tree, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($iterator AS $node) {
echo $node->getIndent() . " => " . (string)$node . PHP_EOL;
}
}
}
<?php
namespace Haml;
class Node implements \RecursiveIterator, \Countable
{
protected $parent = null;
protected $children = [];
protected $ptr = 0;
protected $content = '';
protected $indent = 0;
public function __construct($content, $indent)
{
$this->setContent($content);
$this->setIndent($indent);
}
public function getContent()
{
return $this->content;
}
public function setContent($content)
{
$this->content = strval($content);
}
public function getIndent()
{
return $this->indent;
}
public function setIndent($indent)
{
$this->indent = intval($indent);
}
public function getParent()
{
return $this->parent;
}
public function setParent(Node $node)
{
$this->parent = $node;
}
public function hasParent()
{
return $this->parent instanceof Node;
}
public function addChild(Node $node)
{
$node->setParent($this);
return \array_push($this->children, $node);
}
public function getChildren()
{
return $this->current();
}
public function hasChildren()
{
return count($this->current()) > 0;
}
public function current()
{
return $this->children[$this->key()];
}
public function key()
{
$keys = \array_keys($this->children);
return $keys[$this->ptr];
}
public function next()
{
$this->ptr++;
}
public function rewind()
{
$this->ptr = 0;
}
public function valid()
{
$keys = \array_keys($this->children);
return isset($keys[$this->ptr]);
}
public function count()
{
return count($this->children);
}
public function __toString()
{
return $this->getContent();
}
}
0 => !!! 1.1
0 => %html
1 => %head
2 => %meta{ :http-equiv => 'Content-Type',
:content => 'application/xhtml+xml;charset=utf-8' }
2 => - if ($title)
3 => %title= $title
2 => - else
3 => %title= $pagename
2 => %p Some text |
And some more text |
And yet more text |
1 => %body
2 => #header
3 => %h1 Example page
3 => - if ($slogan)
4 => %span= $slogan
2 => %p Some text |
And some more text |
And yet more text |
2 => #content
3 => %table.config.list
4 => %tr
5 => %th ID
5 => %th Name
5 => %th Value
4 => - foreach ($config as $c)
5 => %tr[$c]
6 => %td= $c->ID
6 => %td= $c->name
6 => %td= $c->value
2 => #footer
3 => %span.author Random Hacker
!!! 1.1
%html
%head
%meta{ :http-equiv => 'Content-Type',
:content => 'application/xhtml+xml;charset=utf-8' }
- if ($title)
%title= $title
- else
%title= $pagename
%p Some text |
And some more text |
And yet more text |
%body
#header
%h1 Example page
- if ($slogan)
%span= $slogan
%p Some text |
And some more text |
And yet more text |
#content
%table.config.list
%tr
%th ID
%th Name
%th Value
- foreach ($config as $c)
%tr[$c]
%td= $c->ID
%td= $c->name
%td= $c->value
#footer
%span.author Random Hacker
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment