1<?php 2 3/* 4 This file is part of ActiveLink PHP XML Package (www.active-link.com). 5 Copyright (c) 2002-2004 by Zurab Davitiani 6 7 You can contact the author of this software via E-mail at 8 hattrick@mailcan.com 9 10 ActiveLink PHP XML Package is free software; you can redistribute it and/or modify 11 it under the terms of the GNU Lesser General Public License as published by 12 the Free Software Foundation; either version 2.1 of the License, or 13 (at your option) any later version. 14 15 ActiveLink PHP XML Package is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU Lesser General Public License for more details. 19 20 You should have received a copy of the GNU Lesser General Public License 21 along with ActiveLink PHP XML Package; if not, write to the Free Software 22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23*/ 24 25/** 26 * Tag class provides a base for parsing, modifying, outputting and creating XML tags 27 * @class Tag 28 * @package org.active-link.xml 29 * @author Zurab Davitiani 30 * @version 0.4.0 31 * @see XML 32 */ 33 34class Tag { 35 36 // protected variables 37 var $tagStartOpen; 38 var $tagStartClose; 39 var $tagClose; 40 var $tagEndOpen; 41 var $tagEndClose; 42 var $tagName; 43 var $tagContent; 44 var $tagAttributes; 45 var $tagAttributeSeparator; 46 var $tagAttributeSeparators; 47 var $tagAttributeAssignment; 48 var $tagAttributeValueQuote; 49 var $FORMAT_NONE; 50 var $FORMAT_INDENT; 51 var $tagFormat; 52 var $tagFormatIndentLevel; 53 var $tagFormatEndTag; 54 var $tagFormatNewLine = "\n"; 55 var $tagFormatIndent = "\t"; 56 57 /** 58 * Constructor creates a tag object with the specified name and tag content 59 * @method Tag 60 * @param optional string name 61 * @param optional string content 62 * @returns none 63 */ 64 function Tag($name = "", $content = "") { 65 $this->tagStartOpen = "<"; 66 $this->tagStartClose = ">"; 67 $this->tagClose = "/>"; 68 $this->tagEndOpen = "</"; 69 $this->tagEndClose = ">"; 70 $this->setTagName($name); 71 $this->setTagContent($content); 72 $this->tagAttributes = array(); 73 $this->tagAttributeSeparator = " "; 74 $this->tagAttributeSeparators = array(" ", "\n", "\r", "\t"); 75 $this->tagAttributeAssignment = "="; 76 $this->tagAttributeValueQuote = '"'; 77 $this->FORMAT_NONE = 0; 78 $this->FORMAT_INDENT = 1; 79 $this->tagFormat = $this->FORMAT_NONE; 80 $this->tagFormatIndentLevel = 0; 81 $this->tagFormatEndTag = false; 82 } 83 84 /** 85 * Find out whether attribute exists 86 * @method attributeExists 87 * @param string attrName 88 * @returns true if attribute exists, false otherwise 89 */ 90 function attributeExists($attrName) { 91 return array_key_exists($attrName, $this->tagAttributes); 92 } 93 94 /** 95 * Get attribute value by its name 96 * @method getTagAttribute 97 * @param string attrName 98 * @returns string attribute value 99 */ 100 function getTagAttribute($attrName) { 101 return $this->tagAttributes[$attrName]; 102 } 103 104 /** 105 * Get tag content string 106 * @method getTagContent 107 * @returns string tag content 108 */ 109 function getTagContent() { 110 return $this->tagContent; 111 } 112 113 /** 114 * Get tag name string 115 * @method getTagName 116 * @returns string tag name 117 */ 118 function getTagName() { 119 return $this->tagName; 120 } 121 122 /** 123 * Get complete tag string with its attributes and content 124 * @method getTagString 125 * @returns string tag string 126 */ 127 function getTagString() { 128 $formatTagBegin = ""; 129 $formatTagEnd = ""; 130 $formatContent = ""; 131 if($this->tagFormat == $this->FORMAT_INDENT) { 132 if($this->tagFormatIndentLevel > 0) 133 $formatTagBegin = $this->tagFormatNewLine . str_repeat($this->tagFormatIndent, $this->tagFormatIndentLevel); 134 if($this->tagFormatEndTag) 135 $formatTagEnd = $this->tagFormatNewLine . str_repeat($this->tagFormatIndent, $this->tagFormatIndentLevel); 136 } 137 $tagString = $formatTagBegin . $this->getTagStringBegin() . $formatContent . $this->tagContent . $formatTagEnd . $this->getTagStringEnd(); 138 return $tagString; 139 } 140 141 /** 142 * Get beginning of the tag string, i.e. its name attributes up until tag contents 143 * @method getTagStringBegin 144 * @returns string beginning of the tag string 145 */ 146 function getTagStringBegin() { 147 $tagString = ""; 148 if($this->tagName != "") { 149 $tagString .= $this->tagStartOpen . $this->tagName; 150 foreach($this->tagAttributes as $attrName => $attrValue) { 151 $tagString .= $this->tagAttributeSeparator . $attrName . $this->tagAttributeAssignment . $this->tagAttributeValueQuote . $attrValue . $this->tagAttributeValueQuote; 152 } 153 if($this->tagContent == "") 154 $tagString .= $this->tagAttributeSeparator . $this->tagClose; 155 else 156 $tagString .= $this->tagStartClose; 157 } 158 return $tagString; 159 } 160 161 /** 162 * Get ending of the tag string, i.e. its closing tag 163 * @method getTagStringEnd 164 * @returns string close tag if tag is not short-handed, empty string otherwise 165 */ 166 function getTagStringEnd() { 167 $tagString = ""; 168 if($this->tagName != "" && $this->tagContent != "") 169 $tagString .= $this->tagEndOpen . $this->tagName . $this->tagEndClose; 170 return $tagString; 171 } 172 173 /** 174 * Remove all tag attributes 175 * @method removeAllAttributes 176 * @returns none 177 */ 178 function removeAllAttributes() { 179 $this->tagAttributes = array(); 180 } 181 182 /** 183 * Remove a tag attribute by its name 184 * @method removeAttribute 185 * @returns none 186 */ 187 function removeAttribute($attrName) { 188 unset($this->tagAttributes[$attrName]); 189 } 190 191 /** 192 * Reset the tag object - set name, content to empty strings, and reset all attributes 193 * @method resetTag 194 * @returns none 195 */ 196 function resetTag() { 197 $this->setTagName(""); 198 $this->setTagContent(""); 199 $this->removeAllAttributes(); 200 } 201 202 /** 203 * Create or modify an existing attribute by supplying attribute name and value 204 * @method setAttribute 205 * @param string attrName 206 * @param string attrValue 207 * @returns none 208 */ 209 function setAttribute($attrName, $attrValue) { 210 $this->tagAttributes[$attrName] = $attrValue; 211 } 212 213 /** 214 * Set contents of the tag 215 * @method setTagContent 216 * @param string content 217 * @returns none 218 */ 219 function setTagContent($content) { 220 $this->tagContent = $content; 221 } 222 223 /** 224 * Set tag formatting option by specifying tagFormat to 0 (none), or 1 (indented) 225 * @method setTagFormat 226 * @param int tagFormat 227 * @param optional int tagFormatIndentLevel 228 * @returns none 229 */ 230 function setTagFormat($tagFormat, $tagFormatIndentLevel = 0) { 231 $this->tagFormat = $tagFormat; 232 $this->tagFormatIndentLevel = $tagFormatIndentLevel; 233 } 234 235 /** 236 * Set whether closing of the tag should be formatted or not 237 * @method setTagFormatEndTag 238 * @param optional boolean formatEndTag 239 * @returns none 240 */ 241 function setTagFormatEndTag($formatEndTag = true) { 242 $this->tagFormatEndTag = $formatEndTag; 243 } 244 245 /** 246 * Parse a string containing a tag into the tag object, this will parse the first tag found 247 * @method setTagFromString 248 * @param string tagString 249 * @returns array array of [0]=>index of the beginning of the tag, [1]=>index where tag ended 250 */ 251 function setTagFromString($tagString) { 252 $i = 0; 253 $j = 0; 254 $tagStartOpen = $tagStartClose = $tagNameStart = $tagNameEnd = $tagContentStart = $tagContentEnd = $tagEndOpen = $tagEndClose = 0; 255 $tagName = $tagContent = ""; 256 $tagShort = false; 257 $tagAttributes = array(); 258 $success = true; 259 $tagFound = false; 260 while(!$tagFound && $i < strlen($tagString)) { 261 // look for start tag character 262 $i = strpos($tagString, $this->tagStartOpen, $i); 263 if($i === false) 264 break; 265 // if tag name starts from alpha character we found the tag 266 if(ctype_alpha(substr($tagString, $i + 1, 1))) 267 $tagFound = true; 268 // else continue searching 269 else 270 $i ++; 271 } 272 // if no tag found set success to false 273 if(!$tagFound) 274 $success = false; 275 // if so far so good continue with found tag name 276 if($success) { 277 $tagStartOpen = $i; 278 $tagNameStart = $i + 1; 279 // search where tag name would end 280 // search for a space separator to account for attributes 281 $separatorPos = array(); 282 for($counter = 0; $counter < count($this->tagAttributeSeparators); $counter ++) { 283 $separatorPosTemp = strpos($tagString, $this->tagAttributeSeparators[$counter], $tagStartOpen); 284 if($separatorPosTemp !== false) 285 $separatorPos[] = $separatorPosTemp; 286 } 287 //$i = strpos($tagString, $this->tagAttributeSeparator, $tagStartOpen); 288 if(count($separatorPos) > 0) 289 $i = min($separatorPos); 290 else 291 $i = false; 292 // search for tag close character 293 $j = strpos($tagString, $this->tagStartClose, $tagStartOpen); 294 // search for short tag (no content) 295 $k = strpos($tagString, $this->tagClose, $tagStartOpen); 296 // if tag close character is not found then no tag exists, set success to false 297 if($j === false) 298 $success = false; 299 // if tag short close found before tag close, then tag is short 300 if($k !== false && $k < $j) 301 $tagShort = true; 302 } 303 // if so far so good set tag name correctly 304 if($success) { 305 // if space separator not found or it is found after the tag close char 306 if($i === false || $i > $j) { 307 if($tagShort) 308 $tagNameEnd = $k; 309 else 310 $tagNameEnd = $j; 311 $tagStartClose = $j; 312 } 313 // else if tag attributes exist 314 else { 315 $tagNameEnd = $i; 316 $tagStartClose = $j; 317 // parse attributes 318 $tagAttributesStart = $i + strlen($this->tagAttributeSeparator); 319 $attrString = trim(substr($tagString, $tagAttributesStart, $j - $tagAttributesStart)); 320 $attrArray = explode($this->tagAttributeValueQuote, $attrString); 321 $attrCounter = 0; 322 while($attrCounter < count($attrArray) - 1) { 323 $attributeName = trim(str_replace($this->tagAttributeAssignment, "", $attrArray[$attrCounter])); 324 $attributeValue = $attrArray[$attrCounter + 1]; 325 $tagAttributes[$attributeName] = $attributeValue; 326 $attrCounter += 2; 327 } 328 } 329 $tagName = rtrim(substr($tagString, $tagNameStart, $tagNameEnd - $tagNameStart)); 330 if(!$tagShort) { 331 $tagContentStart = $tagStartClose + 1; 332 // look for ending of the tag after tag content 333 $j = $tagContentStart; 334 $tagCloseFound = false; 335 // while loop will find the k-th tag close 336 // start with one since we have one tag open 337 $k = 1; 338 while(!$tagCloseFound && $success) { 339 // find k-th tag close from j 340 $n = $j - 1; 341 for($skip = 0; $skip < $k; $skip ++) { 342 $n ++; 343 $tempPos = strpos($tagString, $this->tagEndOpen . $tagName . $this->tagEndClose, $n); 344 if($tempPos !== false) 345 $n = $tempPos; 346 else { 347 $success = false; 348 break; 349 } 350 } 351 // if success, find number of tag opens before the tag close 352 $k = 0; 353 if($success) { 354 $tempString = substr($tagString, $j, $n - $j); 355 $tempNewPos = 0; 356 do { 357 $tempPos = strpos($tempString, $this->tagStartOpen . $tagName, $tempNewPos); 358 if($tempPos !== false) { 359 $tempPosChar = substr($tempString, $tempPos + strlen($this->tagStartOpen . $tagName), 1); 360 $tagEndArray = $this->tagAttributeSeparators; 361 $tagEndArray[] = $this->tagEndClose; 362 $tempPosTagEnded = array_search($tempPosChar, $tagEndArray); 363 if($tempPosTagEnded !== false && $tempPosTagEnded !== NULL) { 364 $tempStartClose = strpos($tempString, $this->tagStartClose, $tempPos); 365 $tempStartShortClose = strpos($tempString, $this->tagClose, $tempPos); 366 // if open tag found increase counter 367 if($tempStartClose !== false && ($tempStartShortClose === false || $tempStartClose < $tempStartShortClose)) 368 $k ++; 369 $tempNewPos = $tempPos + strlen($this->tagStartOpen . $tagName); 370 } 371 else 372 $tempNewPos = $tempPos + strlen($this->tagStartOpen . $tagName); 373 } 374 } while($tempPos !== false); 375 } 376 // if no tags opened we found the tag close 377 if($k == 0) 378 $tagCloseFound = true; 379 // else set new j 380 else { 381 $j = $n + strlen($this->tagEndOpen . $tagName . $this->tagEndClose); 382 } 383 } 384 if($tagCloseFound) 385 $i = $n; 386 else 387 $success = false; 388 } 389 } 390 // if so far so good, then we have everything we need! set the object 391 if($success) { 392 if(!$tagShort) { 393 $tagContentEnd = $i; 394 $tagContent = substr($tagString, $tagContentStart, $tagContentEnd - $tagContentStart); 395 $tagEndOpen = $i; 396 $tagEndClose = $tagEndOpen + strlen($this->tagEndOpen . $tagName . $this->tagEndClose); 397 } 398 else 399 $tagEndClose = $tagStartClose + strlen($this->tagStartClose); 400 $this->setTagName($tagName); 401 $this->setTagContent($tagContent); 402 $this->tagAttributes = $tagAttributes; 403 } 404 if($success) 405 return array($tagStartOpen, $tagEndClose); 406 else 407 return false; 408 } 409 410 /** 411 * Set tag name 412 * @method setTagName 413 * @param string name 414 * @returns none 415 */ 416 function setTagName($name) { 417 $this->tagName = $name; 418 } 419 420} 421 422?> 423