1<?php 2 3/** 4 * Formats any html output (must be valid xml where every tag opened is closed) 5 * using a single tab for indenting. 'pre' and other whitespace sensitive 6 * tags should not be affected. 7 * 8 * It is not recommended to use this on every template if you render multiple 9 * templates per page, you should only use it once on the main page template so that 10 * everything is formatted in one pass. 11 * 12 * This software is provided 'as-is', without any express or implied warranty. 13 * In no event will the authors be held liable for any damages arising from the use of this software. 14 * 15 * @author Jordi Boggiano <j.boggiano@seld.be> 16 * @copyright Copyright (c) 2008, Jordi Boggiano 17 * @license http://dwoo.org/LICENSE Modified BSD License 18 * @link http://dwoo.org/ 19 * @version 1.0.0 20 * @date 2008-10-23 21 * @package Dwoo 22 */ 23class Dwoo_Filter_html_format extends Dwoo_Filter 24{ 25 /** 26 * tab count to auto-indent the source 27 * 28 * @var int 29 */ 30 protected static $tabCount = -1; 31 32 /** 33 * stores the additional data (following a tag) of the last call to open/close/singleTag 34 * 35 * @var string 36 */ 37 protected static $lastCallAdd = ''; 38 39 /** 40 * formats the input using the singleTag/closeTag/openTag functions 41 * 42 * It is auto indenting the whole code, excluding <textarea>, <code> and <pre> tags that must be kept intact. 43 * Those tags must however contain only htmlentities-escaped text for everything to work properly. 44 * Inline tags are presented on a single line with their content 45 * 46 * @param Dwoo_Core $dwoo the dwoo instance rendering this 47 * @param string $input the xhtml to format 48 * @return string formatted xhtml 49 */ 50 public function process($input) 51 { 52 self::$tabCount = -1; 53 54 // auto indent all but textareas & pre (or we have weird tabs inside) 55 $input = preg_replace_callback("#(<[^>]+>)(\s*)([^<]*)#", array('self', 'tagDispatcher'), $input); 56 57 return $input; 58 } 59 60 /** 61 * helper function for format()'s preg_replace call 62 * 63 * @param array $input array of matches (1=>tag, 2=>whitespace(optional), 3=>additional non-html content) 64 * @return string the indented tag 65 */ 66 protected static function tagDispatcher($input) 67 { 68 // textarea, pre, code tags and comments are to be left alone to avoid any non-wanted whitespace inside them so it just outputs them as they were 69 if (substr($input[1],0,9) == "<textarea" || substr($input[1],0,4) == "<pre" || substr($input[1],0,5) == "<code" || substr($input[1],0,4) == "<!--" || substr($input[1],0,9) == "<![CDATA[") { 70 return $input[1] . $input[3]; 71 } 72 // closing textarea, code and pre tags and self-closed tags (i.e. <br />) are printed as singleTags because we didn't use openTag for the formers and the latter is a single tag 73 if (substr($input[1],0,10) == "</textarea" || substr($input[1],0,5) == "</pre" || substr($input[1],0,6) == "</code" || substr($input[1],-2) == "/>") { 74 return self::singleTag($input[1],$input[3],$input[2]); 75 } 76 // it's the closing tag 77 if ($input[0][1]=="/"){ 78 return self::closeTag($input[1],$input[3],$input[2]); 79 } 80 // opening tag 81 return self::openTag($input[1],$input[3],$input[2]); 82 } 83 84 /** 85 * returns an open tag and adds a tab into the auto indenting 86 * 87 * @param string $tag content of the tag 88 * @param string $add additional data (anything before the following tag) 89 * @param string $whitespace white space between the tag and the additional data 90 * @return string 91 */ 92 protected static function openTag($tag,$add,$whitespace) 93 { 94 $tabs = str_pad('',self::$tabCount++,"\t"); 95 96 if (preg_match('#^<(a|label|option|textarea|h1|h2|h3|h4|h5|h6|strong|b|em|i|abbr|acronym|cite|span|sub|sup|u|s|title)(?: [^>]*|)>#', $tag)) { 97 // if it's one of those tag it's inline so it does not require a leading line break 98 $result = $tag . $whitespace . str_replace("\n","\n".$tabs,$add); 99 } elseif (substr($tag,0,9) == '<!DOCTYPE') { 100 // it's the doctype declaration so no line break here either 101 $result = $tabs . $tag; 102 } else { 103 // normal block tag 104 $result = "\n".$tabs . $tag; 105 106 if (!empty($add)) { 107 $result .= "\n".$tabs."\t".str_replace("\n","\n\t".$tabs,$add); 108 } 109 } 110 111 self::$lastCallAdd = $add; 112 113 return $result; 114 } 115 116 /** 117 * returns a closing tag and removes a tab from the auto indenting 118 * 119 * @param string $tag content of the tag 120 * @param string $add additional data (anything before the following tag) 121 * @param string $whitespace white space between the tag and the additional data 122 * @return string 123 */ 124 protected static function closeTag($tag,$add,$whitespace) 125 { 126 $tabs = str_pad('',--self::$tabCount,"\t"); 127 128 // if it's one of those tag it's inline so it does not require a leading line break 129 if (preg_match('#^</(a|label|option|textarea|h1|h2|h3|h4|h5|h6|strong|b|em|i|abbr|acronym|cite|span|sub|sup|u|s|title)>#', $tag)) { 130 $result = $tag . $whitespace . str_replace("\n","\n".$tabs,$add); 131 } else { 132 $result = "\n".$tabs.$tag; 133 134 if (!empty($add)) { 135 $result .= "\n".$tabs."\t".str_replace("\n","\n\t".$tabs,$add); 136 } 137 } 138 139 self::$lastCallAdd = $add; 140 141 return $result; 142 } 143 144 /** 145 * returns a single tag with auto indenting 146 * 147 * @param string $tag content of the tag 148 * @param string $add additional data (anything before the following tag) 149 * @return string 150 */ 151 protected static function singleTag($tag,$add,$whitespace) 152 { 153 $tabs = str_pad('',self::$tabCount,"\t"); 154 155 // if it's img, br it's inline so it does not require a leading line break 156 // if it's a closing textarea, code or pre tag, it does not require a leading line break either or it creates whitespace at the end of those blocks 157 if (preg_match('#^<(img|br|/textarea|/pre|/code)(?: [^>]*|)>#', $tag)) { 158 $result = $tag.$whitespace; 159 160 if (!empty($add)) { 161 $result .= str_replace("\n","\n".$tabs,$add); 162 } 163 } else { 164 $result = "\n".$tabs.$tag; 165 166 if (!empty($add)) { 167 $result .= "\n".$tabs.str_replace("\n","\n".$tabs,$add); 168 } 169 } 170 171 self::$lastCallAdd = $add; 172 173 return $result; 174 } 175} 176