Skip to content

Instantly share code, notes, and snippets.

@asaokamei
Created September 19, 2012 02:39
Show Gist options
  • Save asaokamei/3747335 to your computer and use it in GitHub Desktop.
Save asaokamei/3747335 to your computer and use it in GitHub Desktop.
PHP HTML Tag Generator Class.
<?php
require_once( __DIR__ . '/Tags.php' );
// +--------------------------------------------+
// construction.
$tags = new Tags();
// +--------------------------------------------+
// simple case for anchor link.
echo $tags->a( 'link' )->href( 'tags.php' );
// <a href="tags.php">link</a>
// +--------------------------------------------+
// structured ul > li.
$ul = $tags->ul(
$tags->li( 'list #1' ),
$tags->li( $tags->img()->src( 'img.gif' ) ),
'just a text'
);
echo $ul;
/*
<ul>
<li>list #1</li>
<li><img src="img.gif" /></li>
just a text
</ul>
*/
// +--------------------------------------------+
// select > optgroup > option structure.
$lang = array(
// array( value, option name, optgroup ), ...
array( 'zhi', 'chinese', 'asia' ),
array( 'jpn', 'japanese', 'asia' ),
array( 'kor', 'korean', 'asia' ),
array( 'eng', 'english' ),
array( 'fra', 'french', 'europe' ),
array( 'ger', 'german', 'europe' ),
array( 'spa', 'spanish', 'europe' ),
);
$select = $tags->select()->name( 'language' );
$groups = array();
foreach( $lang as $item ) // loop on languages.
{
$option = $tags->option( $item[1] )->value( $item[0] );
if( isset( $item[2] ) ) // has an optgroup.
{
if( !isset( $groups[ $item[2] ] ) ) { // create new optgroup.
$groups[ $item[2] ] = $tags->optgroup()->label( $item[2] );
$select->contain_( $groups[ $item[2] ] );
}
$groups[ $item[2] ]->contain_( $option );
}
else {
$select->contain_( $option );
}
}
echo $select;
/*
<select name="language">
<optgroup label="asia">
<option value="zhi">chinese</option>
<option value="jpn">japanese</option>
<option value="kor">korean</option>
</optgroup>
<option value="eng">english</option>
<optgroup label="europe">
<option value="fra">french</option>
<option value="ger">german</option>
<option value="spa">spanish</option>
</optgroup>
</select>
*/
// +--------------------------------------------+
// input and with array.
$input = $tags->input()->type( 'check' )->name( 'checkMe' )->value( 'Yeap' );
echo $input;
$input->walk( function( $tags ) {
if( isset( $tags->attributes['name'] ) ) $tags->attributes['name'].='[]';
} );
echo $input . "\n";
/*
<input type="check" name="radioMe" value="Yeap" />
<input type="check" name="radioMe[]" value="Yeap" />
*/
// +--------------------------------------------+
// live and dead tag.
echo $tags->span( 'this is ', $tags->strong( 'strong' ), ' word. ' ) . "\n";
echo $tags->span( 'this is '. $tags->strong( 'strong' ). ' word. ' ) . "\n";
// <span>this is <strong>strong</strong> word. </span>
// <span>this is <strong>strong</strong> word. </span>
// +--------------------------------------------+
// not so clean output.
echo $tags->div(
$tags->ul(
$tags->li( 'list' )
)
);
/*
<div><ul><li>list</li></ul>
</div>
*/
<?php
/**
* @method Tags a()
* @method Tags href()
* @method Tags target()
* @method Tags style()
* @method Tags div()
* @method Tags input()
* @method Tags value()
* @method Tags required()
* @method Tags p()
* @method Tags bold()
* @method Tags i()
* @method Tags em()
* @method Tags option()
* @method Tags checked
* @method Tags optgroup
* @method Tags label
* @method Tags ul
* @method Tags nl
* @method Tags li
* @method Tags table
* @method Tags tr
* @method Tags th
* @method Tags td
* @method Tags span
* @method Tags dl
* @method Tags dd
* @method Tags dt
* @method Tags h1
* @method Tags h2
* @method Tags h3
* @method Tags h4
* @method Tags form
* @method Tags action
* @method Tags method
* @method Tags strong
*/
class Tags
{
/** @var null name of tag, such as span */
protected $tagName = null;
/** @var array array of contents */
protected $contents = array();
/** @var array array of attributes */
public $attributes = array();
/** @var bool for form element's name */
protected $multiple = false;
/** @var array normalize tag name */
public static $normalize_tag = array(
'b' => 'strong',
'bold' => 'strong',
'italic' => 'i',
'image' => 'img',
'item' => 'li',
'order' => 'ol',
'number' => 'nl',
);
/** @var array tags without contents */
public static $tag_no_body = array(
'br', 'img', 'input',
);
/** @var array in-line tags */
public static $tag_span = array(
'span', 'p', 'strong', 'i', 'sub', 'li', 'a', 'label',
);
/** @var array how to connect attribute values */
public static $attribute_connectors = array(
'class' => ' ',
'style' => '; ',
);
/** @var string encoding */
public static $encoding = 'UTF-8';
/** @var bool true for tags such as <img /> */
public $noBodyTag = false;
// +----------------------------------------------------------------------+
// constructions and static methods
// +----------------------------------------------------------------------+
/**
* Start Tag object, with or without tag name.
*
* @param null $tagName
* @param null $contents
* @return Tags
*/
public function __invoke( $tagName=NULL, $contents=NULL ) {
return $this->_( $tagName, $contents );
}
/**
* construction of Tag object.
*
* @param string|null $tagName
* @param null|string $contents
* @return Tags
*/
public function __construct( $tagName=null, $contents=null )
{
$this->setTagName_( $tagName );
$this->setContents_( $contents );
}
/**
* @param string|null $tagName
* @param null|string $contents
* @return Tags
*/
public function _( $tagName=NULL, $contents=NULL )
{
$class = get_called_class();
return new $class( $tagName, $contents );
}
/**
* set attribute, or tagName if tagName is not set.
*
* @param string $name
* @param array $args
* @return Tags
*/
public function __call( $name, $args )
{
// attribute or tag if not set.
if( is_null( $this->tagName ) ) { // set it as a tag name
return $this->_( $name, $args );
}
else {
$this->setAttribute_( $name, $args );
}
return $this;
}
/**
* make string VERY safe for html.
*
* @param string $value
* @return string
*/
public static function safe_( $value ) {
if( empty( $value ) ) return $value;
return htmlentities( $value, ENT_QUOTES, static::$encoding );
}
/**
* wrap value with closure. use this to avoid encoding attribute values.
*
* @param string $value
* @return callable
*/
public static function wrap_( $value ) {
return function() use( $value ) { return $value; };
}
public function isSpanTag() {
return in_array( $this->tagName, static::$tag_span );
}
public function isNoBodyTag() {
return $this->noBodyTag;
}
// +----------------------------------------------------------------------+
// mostly internal functions
// +----------------------------------------------------------------------+
/**
* set tag name.
*
* @param string $tagName
* @return Tags
*/
protected function setTagName_( $tagName )
{
if( empty( $tagName ) ) return $this;
$tagName = $this->normalize_( $tagName );
if( array_key_exists( $tagName, static::$normalize_tag ) ) {
$tagName = static::$normalize_tag[ $tagName ];
}
$this->tagName = $tagName;
if( in_array( $this->tagName, static::$tag_no_body ) ) {
$this->noBodyTag = true;
}
return $this;
}
/**
* set contents.
*
* @param string|array|Tags $contents
* @return Tags
*/
protected function setContents_( $contents ) {
if( empty( $contents ) ) return $this;
if( is_array( $contents ) ) {
$this->contents = array_merge( $this->contents, $contents );
}
else {
$this->contents[] = $contents;
}
return $this;
}
/**
* set attribute. if connector is not set, attribute is replaced.
*
* @param string $name
* @param string|array $value
* @param bool|string $connector
* @return Tags
*/
protected function setAttribute_( $name, $value, $connector=null )
{
if( is_array( $value ) && !empty( $value ) ) {
foreach( $value as $val ) {
$this->setAttribute_( $name, $val, $connector );
}
return $this;
}
elseif( is_array( $value ) ) {
$value = '';
}
if( $value === false ) return $this; // ignore the property.
$name = $this->normalize_( $name );
if( $value === true ) $value = $name; // same as name (checked="checked")
// set connector if it is not set.
if( $connector === null ) {
$connector = false; // default is to replace value.
if( array_key_exists( $name, static::$attribute_connectors ) ) {
$connector = static::$attribute_connectors[ $name ];
}
}
// set attribute.
if( !isset( $this->attributes[ $name ] ) // new attribute.
|| $connector === false ) { // replace with new value.
$this->attributes[ $name ] = $value;
}
else { // attribute is appended.
$this->attributes[ $name ] .= $connector . $value;
}
return $this;
}
/**
* normalize tag and attribute name: lower case, and remove first _ if exists.
*
* @param string $name
* @return string
*/
protected function normalize_( $name ) {
$name = strtolower( $name );
if( $name[0]=='_') $name = substr( $name, 1 );
$name = str_replace( '_', '-', $name );
return $name;
}
// +----------------------------------------------------------------------+
// methods for setting tags, attributes, and contents.
// +----------------------------------------------------------------------+
/**
* set contents.
*
* @internal param array|string|Tags $contents
* @return Tags
*/
public function contain_()
{
/** @var $args array */
$args = func_get_args();
return $this->setContents_( $args );
}
/**
* set class name. adds to the existing class.
*
* @param string $class
* @param string $connector set FALSE to reset class.
* @return Tags
*/
public function _class( $class, $connector=' ' ) {
return $this->setAttribute_( 'class', $class, $connector );
}
/**
* set style. adds to the existing style.
*
* @param string $style
* @param string $connector set FALSE to reset style.
* @return Tags
*/
public function _style( $style, $connector='; ' ) {
return $this->setAttribute_( 'style', $style, $connector );
}
/**
* @param \Closure $func
* @param string $attribute
*/
public function walk( $func, $attribute=null )
{
if( !$attribute || $this->$attribute || isset( $this->attributes[ $attribute ] ) ) {
$func( $this );
}
if( !empty( $this->contents ) ) {
foreach( $this->contents as $content ) {
if( $content instanceof self ) {
$content->walk( $func, $attribute );
}
}
}
}
// +----------------------------------------------------------------------+
// convert Tags to a string.
// +----------------------------------------------------------------------+
/**
* @param string $head
* @return string
*/
protected function toContents_( $head="" ) {
$html = '';
if( empty( $this->contents ) ) return $html;
foreach( $this->contents as $content ) {
if( !$this->isNoBodyTag() && !$this->isSpanTag() && $html && substr( $html, -1 ) != "\n" ) {
$html .= "\n";
}
if( is_object( $content ) && method_exists( $content, 'toString_' ) ) {
$html .= $content->toString_( $head );
}
else {
$html .= $head . (string) $content;
}
}
return $html;
}
/**
* @return string
*/
protected function toAttribute_() {
$attr = '';
if( !empty( $this->attributes ) )
foreach( $this->attributes as $name => $value ) {
if( $value instanceof \Closure ) {
$value = $value(); // wrapped by closure. use it as is.
}
else {
$value = static::safe_( $value ); // make it very safe.
}
$attr .= " {$name}=\"{$value}\"";
}
return $attr;
}
/**
* @param string $head
* @return string
*/
protected function toString_( $head='' )
{
$html = $head;
if( $this->isNoBodyTag() ) {
// create tag without content, such as <tag attributes... />
$html .= "<{$this->tagName}" . $this->toAttribute_() . ' />';
}
elseif( $this->isSpanTag() || count( $this->contents ) == 1 ) {
// short tag such as <tag>only one content</tag>
$html .= "<{$this->tagName}" . $this->toAttribute_() . ">";
$html .= $this->toContents_();
$html .= "</{$this->tagName}>";
}
else { // create tag with contents inside.
$html .= "<{$this->tagName}" . $this->toAttribute_() . ">";
$html .= "\n";
$html .= $this->toContents_( $head . ' ' );
if( substr( $html, -1 ) != "\n" ) $html .= "\n";
$html .= $head . "</{$this->tagName}>";
}
if( !$this->isSpanTag() && !$this->isNoBodyTag() ) {
// add new-line, except for in-line tags.
$html .= "\n";
}
return $html;
}
/**
* @return string
*/
public function __toString()
{
return $this->toString_();
}
// +----------------------------------------------------------------------+
}
<?php
require_once( __DIR__ . '/Tags.php' );
class Tags_Test extends \PHPUnit_Framework_TestCase
{
/** @var Tags */
public $tags;
public function setUp()
{
$this->tags = new Tags();
}
// +----------------------------------------------------------------------+
public function test_underscore_to_hyphen()
{
$span = (string) $this->tags->span( 'text span' )->data_type( 'dataType' );
$this->assertContains( 'data-type="dataType"', $span );
}
public function test_quote_safe()
{
$unsafe = 'unsafe" string';
$text = (string) $this->tags->input()->value( $unsafe );
$this->assertContains( htmlentities( $unsafe ), $text );
$text = (string) $this->tags->input()->value( Tags::wrap_( $unsafe ) );
$this->assertContains( $unsafe, $text );
}
public function test_inline()
{
$text = (string) $this->tags->p( 'this is ' . $text = (string) $this->tags->bold( 'bold text' ) . '.' );
$this->assertContains( '<strong>bold text</strong>.</p>', $text );
}
public function test_input_required()
{
$text = (string) $this->tags->input()->required( true );
$this->assertContains( '<input required="required" />', $text );
}
public function test_div_in_div_box()
{
$text = (string) $this->tags->div()->_class( 'divClass' )->contain_(
'this is a text',
$this->tags->div(
$this->tags->a( 'link1' )->href( 'do.php' )->target( '_blank' ),
$this->tags->a( 'link2' )->href( 'do1.php' )->target( '_blank' )
),
$this->tags->a( 'link3' )->href( 'do2.php' )
);
$lines = explode( "\n", $text );
$this->assertContains( "<div class=\"divClass\">", $lines[0] );
$this->assertContains( 'this is a text', $lines[1] );
$this->assertContains( "<div>", $lines[2] );
$this->assertContains( '<a href="do.php" target="_blank">link1</a>', trim( $lines[3] ) );
$this->assertContains( '<a href="do1.php" target="_blank">link2</a>', trim( $lines[4] ) );
$this->assertContains( "</div>", $lines[5] );
$this->assertContains( "<a href=\"do2.php\">link3</a>", $lines[6] );
$this->assertContains( "</div>", $lines[7] );
}
public function test_div_box()
{
$text = (string) $this->tags->div();
$this->assertContains( "<div>\n</div>", $text );
$text = (string) $this->tags->div(
'this is a text',
$this->tags->a( 'a link' )->href( 'do.php' )->target( '_blank' ),
$this->tags->a( 'a link' )->href( 'do1.php' )->target( '_blank' ),
$this->tags->a( 'a link' )->href( 'do2.php' )->target( '_blank' )
);
$lines = explode( "\n", $text );
$this->assertContains( "<div>", $lines[0] );
$this->assertContains( 'this is a text', $lines[1] );
$this->assertContains( '<a href="do.php" target="_blank">a link</a>', trim( $lines[2] ) );
$this->assertContains( '<a href="do1.php" target="_blank">a link</a>', trim( $lines[3] ) );
$this->assertContains( '<a href="do2.php" target="_blank">a link</a>', trim( $lines[4] ) );
$this->assertContains( "</div>", $lines[5] );
}
public function test_style()
{
$text = (string) $this->tags->a( 'a link' )->href( 'do.php' )->style( 'style1' )->style( 'style2' );
$this->assertContains( '<a href="do.php" style="style1; style2">a link</a>', $text );
$text = (string) $this->tags->a( 'a link' )->href( 'do.php' )->style( 'style1' )->_style( 'style2', FALSE );
$this->assertContains( '<a href="do.php" style="style2">a link</a>', $text );
}
public function test_class()
{
$text = (string) $this->tags->a( 'a link' )->href( 'do.php' )->_class( 'myClass' )->_class( 'myClass2' );
$this->assertContains( '<a href="do.php" class="myClass myClass2">a link</a>', $text );
$text = (string) $this->tags->a( 'a link' )->href( 'do.php' )->_class( 'myClass' )->_class( 'myClass2', FALSE );
$this->assertContains( '<a href="do.php" class="myClass2">a link</a>', $text );
}
public function test_anchor_link()
{
$text = (string) $this->tags->a( 'a link' )->href( 'do.php' )->target( '_blank' );
$this->assertContains( '<a href="do.php" target="_blank">a link</a>', $text );
}
public function test_alternative_syntax()
{
// alternative syntax
$tags = $this->tags;
$text = (string) $tags( 'a', 'a link' )->href( 'do.php' )->target( '_blank' );
$this->assertContains( '<a href="do.php" target="_blank">a link</a>', $text );
$text = (string) $tags( 'a', 'a link' )->href( 'do.php' )->style( 'style1' )->style( 'style2' );
$this->assertContains( '<a href="do.php" style="style1; style2">a link</a>', $text );
}
}
@litpale
Copy link

litpale commented Jul 11, 2022

Is this optimize on load speed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment