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