00001 <?php
00002
00008 class XmlFormatter extends SObject {
00009 private static $showFormatting;
00010 private static $indent;
00011 private static $curLine = 0;
00012 private static $charsOnLine = 0;
00013 private static $lastError = "";
00014 private static $skipNextLN = false;
00015 private static $noBreakTags = array('bold', 'b', 'i', 'u', 'span', 'bold', 'link', 'a', 'media', 'name', 'sup', 'entity');
00016 private static $lastTag = "";
00017 private static $inPre = false;
00018 private static $inCDATA = false;
00019
00030 public static function format($input, $highlight = false, $lineNumbers = false) {
00031 $input = preg_replace_callback('/(<!\[CDATA\[.*?\]\]>)/ms', array('XmlFormatter', 'cdataReplace'), $input);
00032 $input = preg_replace_callback('/(?!<pre\s*\/>)(<pre[^>]*>.*?<\/pre>)/ms', array('XmlFormatter', 'preReplace'), $input);
00033
00034 $input = str_replace('&', '&', $input);
00035 $xml = xml_parser_create();
00036 xml_parser_set_option($xml, XML_OPTION_SKIP_WHITE, 0);
00037 xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, 0);
00038 if(xml_parse_into_struct($xml, "<xml>$input</xml>", $vals, $index) == 0) {
00039 $code = xml_get_error_code($xml);
00040 $ret = array(xml_error_string($code));
00041 xml_parser_free($xml);
00042 return $ret;
00043 }
00044 self::$inCDATA = false;
00045 self::$inPre = false;
00046 xml_parser_free($xml);
00047
00048 $out = "";
00049 $newVals = array();
00050
00051 $j = 0;
00052 foreach($vals as $i => $v) {
00053 switch($v['type']) {
00054 case 'open':
00055 case 'close':
00056 case 'complete':
00057 $newVals[$j++] = $v;
00058 break;
00059 default:
00060 if($newVals[$j-1]['type'] != 'complete') {
00061 if(!isset($newVals[$j-1]['value']))
00062 $newVals[$j-1]['value'] = "";
00063 $newVals[$j-1]['value'] .= $v['value'];
00064 }
00065 else {
00066 $newVals[$j++] = $v;
00067 $newVals[$j-1]['tag'] = '';
00068 }
00069 break;
00070 }
00071 }
00072
00073 foreach($newVals as $v) {
00074 $ret = self::outputNode($v, $highlight, $lineNumbers);
00075 if($ret != "")
00076 $out .= $ret;
00077 }
00078
00079 $out = preg_replace_callback('/<n1nonxml(.*?)<\/n1nonxml>/ms', array('XmlFormatter', 'myb64d'), $out);
00080 $out = preg_replace_callback('/<n2nonxml>(.*?)<\/n2nonxml>/ms', array('XmlFormatter', 'myb64d'), $out);
00081
00082 return trim($out);
00083 }
00084
00085 private static function printLineNumber($lineNumbers) {
00086 if(!$lineNumbers)
00087 return "\n";
00088 self::$curLine++;
00089 self::$charsOnLine = 0;
00090 if(self::$curLine < 10)
00091 return "\n<b> " . self::$curLine . "</b>: ";
00092 else if(self::$curLine < 100)
00093 return "\n<b> " . self::$curLine . "</b>: ";
00094 else if(self::$curLine < 1000)
00095 return "\n<b> " . self::$curLine . "</b>: ";
00096 else
00097 return "\n<b>" . self::$curLine . "</b>: ";
00098 }
00099
00100 private static function highlight($text, $highlight, $color) {
00101 if($highlight)
00102 return "<span style=\"color:$color\">" . htmlspecialchars($text) . "</span>";
00103 else
00104 return $text;
00105 }
00106
00107 private static function replaceLN($matches) {
00108 return self::printLineNumber(true);
00109 }
00110
00111 private static function formatValue($value, $indent, $highlight, $lineNumbers, $keepFormatting = false) {
00112 if(!$keepFormatting) {
00113 $value = preg_replace("/[ \n\r\t]+/", " ", $value);
00114 $lineWidth = 100 - strlen($indent);
00115 if($lineNumbers) {
00116 $ret = wordwrap($value, $lineWidth, "AXXYYZYYXX$indent");
00117 $ret = preg_replace_callback("/AXXYYZYYXX/", array('self', 'replaceLN'), $ret);
00118 }
00119 else
00120 $ret = wordwrap($value, $lineWidth, "\n$indent");
00121 }
00122 else {
00123 if($lineNumbers) {
00124 $ret = str_replace("\n", "AXXYYZYYXX", $value);
00125 $ret = str_replace("\r", "AXXYYZYYXX", $ret);
00126 $ret = preg_replace_callback("/AXXYYZYYXX/", array('self', 'replaceLN'), $ret);
00127 }
00128 else if($highlight) {
00129 $ret = str_replace("\n", "<br />", $value);
00130 $ret = str_replace("\r", "<br />", $ret);
00131 }
00132 else {
00133 $ret = str_replace("\r", "\n", $value);
00134 }
00135 }
00136 $tmp = explode("\n", $ret);
00137 self::$charsOnLine = strlen($tmp[count($tmp) - 1]);
00138 return $ret;
00139 }
00140
00141 private static function formatCDATA($cdata, $highlight, $lineNumbers) {
00142 if($lineNumbers) {
00143 $ret = str_replace("\n", "\nAXXYYZYYXX", $cdata);
00144 $ret = str_replace("\r", "\rAXXYYZYYXX", $ret);
00145 $ret = preg_replace_callback("/AXXYYZYYXX/", array('self', 'replaceLN'), $ret);
00146 }
00147 else if($highlight) {
00148 $ret = str_replace("\n", "<br />", $cdata);
00149 $ret = str_replace("\r", "<br />", $cdata);
00150 }
00151 else {
00152 $ret = $cdata;
00153 }
00154 return $ret;
00155 }
00156
00157 private static function outputNode($node, $highlight, $lineNumbers) {
00158 if($node['tag'] == 'xml' && $node['level'] == 1)
00159 return "";
00160
00161 $level = $node['level'];
00162 $indent = "";
00163 $level -= 2;
00164
00165 while($level > 0) {
00166 $indent .= " ";
00167 $level--;
00168 }
00169
00170 if(isset($node['value'])) {
00171 if($node['tag'] != 'pre' && !self::$inPre) {
00172 $node['value'] = preg_replace("/\s{2,}/", " ", $node['value']);
00173 }
00174 else
00175 $node['value'] = rtrim($node['value'], " \t\n");
00176 if($node['value'] == "" || $node['value'] == " ")
00177 unset($node['value']);
00178 }
00179
00180 switch($node['type']) {
00181 case 'open':
00182 if($node['tag'] == 'cdata') {
00183 self::$inCDATA = true;
00184 if($highlight)
00185 return '<span style="color:red"><![CDATA[</span>' .
00186 self::formatCDATA(isset($node['value'])?$node['value']:'', true, $lineNumbers);
00187 else
00188 return '<![CDATA[' . self::formatCDATA(isset($node['value'])?$node['value']:'', false, $lineNumbers);
00189 }
00190 $ret = (in_array($node['tag'], self::$noBreakTags) && false && self::$charsOnLine < 80 ? "" : self::printLineNumber($lineNumbers) . "$indent")
00191 . self::highlight("<$node[tag]", $highlight, 'blue');
00192 if(isset($node['attributes'])) {
00193 foreach($node['attributes'] as $a => $v) {
00194 $ret .= " " . self::highlight($a, $highlight, 'green') . "=\"" . self::highlight($v, $highlight, 'red') . "\"";
00195 }
00196 }
00197 $ret .= self::highlight(">", $highlight, 'blue');
00198 if(isset($node['value']) && $node['value'] != "") {
00199 if(!in_array($node['tag'], self::$noBreakTags))
00200 $node['value'] = ltrim($node['value']);
00201 $ret .= self::printLineNumber($lineNumbers) . $indent . (!in_array($node['tag'], self::$noBreakTags) ? " " : "")
00202 . self::formatValue($node['value'], $indent . " ", $highlight, $lineNumbers,
00203 strtolower($node['tag']) == 'pre' ? true : false);
00204 }
00205 if(!in_array($node['tag'], self::$noBreakTags))
00206 self::$charsOnLine = strlen($ret);
00207 else
00208 self::$charsOnLine += 40;
00209 if($node['tag'] == 'pre')
00210 self::$inPre = true;
00211 else
00212 self::$inPre = false;
00213 return $ret;
00214 case 'close':
00215 if($node['tag'] == 'cdata') {
00216 self::$inCDATA = false;
00217 if($highlight)
00218 return self::formatCDATA(isset($node['value'])?$node['value']:'', true, $lineNumbers) . '<span style="color:red">]]></span>';
00219 else
00220 return self::formatCDATA(isset($node['value'])?$node['value']:'', false, $lineNumbers) . ']]>';
00221 }
00222 if(in_array($node['tag'], self::$noBreakTags))
00223 self::$skipNextLN = true;
00224 $ret = self::printLineNumber($lineNumbers) . $indent;
00225 $ret .= self::highlight("</$node[tag]>", $highlight, 'blue');
00226 if(isset($node['value']) && $node['value'] != '')
00227 $ret .= self::formatValue($node['value'], $indent . " ", $highlight, $lineNumbers,
00228 strtolower($node['tag']) == 'pre' ? true : false);
00229 self::$lastTag = $node['tag'];
00230 self::$charsOnLine += strlen($ret);
00231 self::$inPre = false;
00232 return $ret;
00233 case 'complete':
00234 if($node['tag'] == 'cdata') {
00235 if($highlight)
00236 return '<span style="color:red"><![CDATA[</span>'
00237 . self::formatCDATA(isset($node['value'])?$node['value']:'', true, $lineNumbers)
00238 . '<span style="color:red">]]></span>';
00239 else
00240 return '<![CDATA['
00241 . self::formatCDATA(isset($node['value'])?$node['value']:'', false, $lineNumbers)
00242 . ']]>';
00243 }
00244 $ret = (in_array($node['tag'], self::$noBreakTags) && false && self::$charsOnLine < 80 ? "" : self::printLineNumber($lineNumbers) . "$indent")
00245 . self::highlight("<$node[tag]", $highlight, 'blue');
00246 if(in_array($node['tag'], self::$noBreakTags))
00247 self::$skipNextLN = true;
00248 if(isset($node['attributes'])) {
00249 foreach($node['attributes'] as $a => $v) {
00250 $ret .= " " . self::highlight($a, $highlight, 'green') . "=\"" . self::highlight($v, $highlight, 'red') . "\"";
00251 }
00252 }
00253 if(isset($node['value']) && $node['value'] != "") {
00254 if(!in_array($node['tag'], self::$noBreakTags)) {
00255 $node['value'] = ltrim($node['value']);
00256 }
00257 $ret .= self::highlight(">", $highlight, 'blue');
00258 if(!in_array($node['tag'], self::$noBreakTags))
00259 $ret .= self::printLineNumber($lineNumbers) . "$indent" . (strtolower($node['tag']) == 'pre' ? '' : " ");
00260 $ret .= self::formatValue($node['value'], $indent . (in_array($node['tag'], self::$noBreakTags) ? "" : " "), $highlight, $lineNumbers, strtolower($node['tag']) == 'pre' ? true : false);
00261 if(!in_array($node['tag'], self::$noBreakTags))
00262 $ret .= self::printLineNumber($lineNumbers) . "$indent";
00263 $ret .= self::highlight("</$node[tag]>", $highlight, 'blue');
00264 }
00265 else
00266 $ret .= self::highlight(" />", $highlight, 'blue');
00267 self::$lastTag = $node['tag'];
00268 if(!in_array($node['tag'], self::$noBreakTags))
00269 self::$charsOnLine = 0;
00270 else
00271 self::$charsOnLine += strlen($ret);
00272 return $ret;
00273 default:
00274 if(self::$inCDATA)
00275 return self::formatCDATA($node['value'], $highlight, $lineNumbers);
00276 if(isset($node['value']) && $node['value'] != "") {
00277 if(!in_array(self::$lastTag, self::$noBreakTags))
00278 $node['value'] = ltrim($node['value']);
00279 if(self::$skipNextLN) {
00280 $ret = self::formatValue($node['value'], $indent . " ", $highlight, $lineNumbers, self::$inPre);
00281 self::$skipNextLN = false;
00282 }
00283 else
00284 $ret = self::printLineNumber($lineNumbers) . $indent . " " . self::formatValue($node['value'], $indent . " ", $highlight, $lineNumbers, self::$inPre);
00285 }
00286 else
00287 $ret = "";
00288 return $ret;
00289 }
00290 }
00291
00292 public static function myb64d($matches) {
00293 return base64_decode($matches[1]);
00294 }
00295
00296 public static function cdataReplace($matches) {
00297 return "<n2nonxml>" . base64_encode($matches[1]) . "</n2nonxml>";
00298 }
00299
00300 public static function preReplace($matches) {
00301 return "<n1nonxml>" . base64_encode($matches[1]) . "</n1nonxml>";
00302 }
00303
00312 public static function formatXMLSource($xml) {
00313 $raw = str_replace('&', '&amp;', $xml);
00314 $out = self::format($raw, true, true);
00315 if($out === false) {
00316 $out = "<b><span style=\"color:red\">Error</span>: </b>" . self::getLastStaticError();
00317 }
00318 else {
00319 $out = preg_replace_callback("/<span style=\"color:blue\"><media<\/span> <span style=\"color:green\">snapid"
00320 . "<\/span>=\"<span style=\"color:red\">([0-9a-zA-Z_\/-]*)<\/span>\"[^&]*>/",
00321 array('XmlFormatter', 'linkMediaTags'), $out);
00322 }
00323 return "<tt><pre>$out</pre></tt>";
00324 }
00325
00326 private static function linkMediaTags($matches) {
00327 global $PATH;
00328 if(!isset($PATH['view']))
00329 $url = "/snap2/view";
00330 else
00331 $url = $PATH['view'];
00332 $snapid = $matches[1];
00333 if(strpos($snapid, "/") !== false)
00334 $res = SnapResource::lookup($snapid);
00335 else
00336 $res = SnapResource::retrieve($snapid);
00337 if(!$res || !$res->isValid())
00338 return "<span title=\"Invalid resource link\" style=\"background: #E0E0E0; color: red; font-weight: bold\">"
00339 . "$matches[0]</span>";
00340 return "<a class=\"snapMediaLink\" title=\"View resource " . $res->getCanonicalPath()
00341 . "\" target=\"_blank\" href=\"$url" . $res->getCanonicalPath() . "\">$matches[0]</a>";
00342 }
00343
00344 public static function prepareXML($str, $field, $fixPars = true) {
00345
00346 $str = strtr($str, array('<' => '&lt;', '>' => '&gt;', '&' => '&amp;'));
00347
00348
00349 $str = preg_replace_callback('/(?![\\\\])<code>(.*?)<\/code>/ms', array('XmlFormatter', 'cctHelper'), $str);
00350
00351
00352 $str = EscapeParser::parseString($str, $field ? 'In field <b>' . $field . '</b>: ' : '');
00353 if(is_array($str))
00354 return $str;
00355
00356
00357 if($fixPars) {
00358 $str = preg_replace("/\n[\t ]*\n/", "</p><p>", $str);
00359 $str = preg_replace("/\r[\t ]*\r/", "</p><p>", $str);
00360 $str = preg_replace("/\r\n[\t ]*\r\n/", "</p><p>", $str);
00361 }
00362
00363 return $str;
00364 }
00365
00366 public static function cctHelper($matches) {
00367 return '<p><pre><![CDATA[' . base64_encode(htmlspecialchars($matches[1])) . ']]></pre></p>';
00368 }
00369
00378 public static function finalGenerateXML($xmlString) {
00379 $xmlString = preg_replace_callback('/<p><pre><!\[CDATA\[([a-zA-Z0-9+_\/=-]*)\]\]><\/pre><\/p>/',
00380 array('XmlFormatter', 'fixPreBlocks'), $xmlString);
00381 $xmlString = preg_replace_callback('/<span><!\[CDATA\[([a-zA-Z0-9+_\/=-]*)\]\]><\/span>/',
00382 array('XmlFormatter', 'fixLitBlocks'), $xmlString);
00383
00384 $errors = SXMLHelper::validateXML($xmlString, true);
00385
00386 if($errors !== true) {
00387 $errors[] = '<b>The above errors indicate that Snap2 had an internal error. Please report those errors.</b>';
00388 return $errors;
00389 }
00390
00391 return $xmlString;
00392 }
00393
00394 public static function fixPreBlocks($matches) {
00395 return '<pre>' . htmlspecialchars(base64_decode($matches[1])) . '</pre>';
00396 }
00397
00398 public static function fixLitBlocks($matches) {
00399 $str = htmlspecialchars(base64_decode($matches[1]));
00400
00401
00402 $str = preg_replace("/\n[\t ]*\n/", "</p><p>", $str);
00403 $str = preg_replace("/\r[\t ]*\r/", "</p><p>", $str);
00404 $str = preg_replace("/\r\n[\t ]*\r\n/", "</p><p>", $str);
00405
00406 return $str;
00407 }
00408 }
00409
00410 ?>