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 | */ |
---|
23 | class 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 | } |
---|