NVU HTML to PHP Parser
3-10-2008: I never really ended up using this tool.
The simplified template system described elsewhere was working pretty well, but I was faced with making a number of web forms. I hate coding these up by hand because there are always changes, and it's a pain to edit the table layouts. (CSS isn't quite right.) So, I wrote a tool that converts the HTML code into the .PHT format. .PHT is a very small subset of PHP, consisting of echoing object properties, calling object methods, the ternary operator (? :), and while loops that call the next() method.
I decided to edit the web forms in NVU. NVU is a visual HTML editing tool, based on Mozilla Composer, but better. My rationale for using NVU was simple: I like to use it, it's free, and it's better than editing HTML code by hand. It would speed up the process of making templates, possibly significantly. Also, it allows the programmer to have the users edit the forms, then, to build the app behind the forms. Here's what I did:

Whenever I need to introduce a variable substitution, I typed it like this:
$variable $object->method() $object->property
To insert a dollar ($), just put a slash in front of it.
In some places, I needed to loop over error messages. To do this, I typed:
$errors->message
Then, I inserted a comment on the line above it, and a comment on the line below it. The comments are on their own lines. The comment above had this content:
while $errors
The comment below had this comment:
endwhile
In NVU, the comment is inserted from the Insert menu. When you insert the comment, make sure it's on its own line. This will show up as a yellow question mark. In the sources, it'll show the comment followed by a BR tag; don't sweat it -- the compiler will remove the tag.
There's a feature that lets you put more than one template into the HTML file, and have it split out into multiple files. This is done with the @file() directive. The following will save the code below it into "signin.pht".
@file(signin.pht)
I created the templates.html file (see attachment).
Then, I created a page with this content:
<?php include 'HtmlToPht.class.php'; $p = new HtmlToPht( 'templates.html' ); // the name of the file I saved ?>
And executed it. This emitted several .pht files, each one a functioning template in the simple template language.
Here's the code for HtmlToPht.class.php:
/**
* This script converts NVU output to PHT templates.
* It sprinkles a little syntactic sugar over the HTML.
* $thing becomes <?php echo $thing ?>, unless it looks like \$thing, which becomes $thing.
* <!--while $thing--> becomes <?php while( $this->next() ) : ?>
* and <!--endwhile--> becomes <?php endwhile; ?>
* ALSO, if the above comments are followed by a <br />, the break is stripped.
*
* @file(filename.pht) starts writing the data to filename.pht
* The first lines are written to the template's filename, but with a pht extension.
*
* Overall, the code is pretty crude.
*/
class HtmlToPht
{
var $whileStack;
var $indent;
function HtmlToPht( $template )
{
$this->template = $template;
$this->f = fopen( $this->template, 'r' );
$this->newFileName = preg_replace( '/(.html)$/', '', $this->template ).'.pht';
$this->output = fopen( $this->newFileName, 'w' );
$this->whileStack = array();
$this->indent = 0;
while( $line = fgets( $this->f ) )
{
if ( preg_match( '/^<body/i', $line ) ) break;
}
while( $line = fgets( $this->f ) )
{
$line = rtrim($line);
if ( preg_match( '#^@file\\((.+)\\)#', $line, $matches ) )
{
$filename = $matches[1];
if ($this->output)
fclose($this->output);
$this->output = fopen( $filename, 'w' );
}
else if ( preg_match( '#</body>#', $line ) )
{
fclose($this->output);
break;
}
else
{
$indented = false;
$newline = $line;
$newline = $this->replace_while( $newline );
if ($newline != $line)
$indented = true;
$newline = $this->replace_endwhile( $newline );
if ($newline==$line)
$newline = $this->replace_dollars( $newline );
if ($newline)
fwrite( $this->output, str_repeat( ' ', $this->indent ) . $newline . "\n" );
if ($indented)
$this->indent++;
}
}
// clean up empty file
$stat = stat( $this->newFileName );
if ( $stat['size']==0 )
unlink( $this->newFileName );
}
function replace_dollars( $line )
{
$out = $line;
$out = preg_replace( '/([^\\\\])(\\$[a-zA-Z&;()-]+)/', '\\1<?php echo \\2; ?>', $out );
if ($out == $line)
$out = preg_replace( '/^(\\$[a-zA-Z&;()-]+)/', '<?php echo \\1; ?>', $out );
$out = preg_replace( '/(echo \\$[a-zA-Z]+-)>([a-zA-Z()]+;)/', '\\1>\\2', $out );
if ($out == $line)
$out = preg_replace( '/\\\\(\\$[a-zA-Z&;()-]+)/', '\\1', $out );
return $out;
}
function replace_while( $line )
{
$out = $line;
preg_match( '/<!--while \\$([a-zA-Z]+)-->/', $out, $matches );
if ($matches[1])
array_push($this->whileStack, $matches[1]);
$out = preg_replace( '/<!--while \\$([a-zA-Z]+)-->/',
'<?php while( $\\1->next() ) : //begin \\1 ?>', $out );
if ($out != $line)
$out = preg_replace( '#(.+)<br />$#', '\\1', $out );
return $out;
}
function replace_endwhile( $line )
{
$out = $line;
if (preg_match( '/<!--endwhile-->/', $line ) )
{
$while = array_pop($this->whileStack);
$out = preg_replace( '/<!--endwhile-->/',
'<?php endwhile; //end '.$while.' ?>', $out );
if ($out != $line)
$out = preg_replace( '#(.+)<br />$#', '\\1', $out );
$this->indent--;
$this->indent = max( 0, $this->indent );
}
return $out;
}
}
This is an ad-hoc little language. The parser is primitive and difficult to extend.
| Attachment | Size |
|---|---|
| templates.html.txt | 2.35 KB |
| screenshot_nvu.jpg | 38.36 KB |
