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
25import("org.active-link.xml.Tag");
26import("org.active-link.xml.Tree");
27
28/**
29  *	XML class provides a tree-like structure to read/write/modify XML
30  *	@class		XML
31  *	@package	org.active-link.xml
32  *	@author		Zurab Davitiani
33  *	@version	0.4.0
34  *	@extends	Tree
35  *	@requires	Tag, Tree, XMLBranch, XMLLeaf
36  *	@see		Tree
37  */
38
39class XML extends Tree {
40
41	// protected variables
42	var $tag;
43	var $pathSeparator;
44
45	/**
46	  *	If argument is an XML String it parses the string into XML object
47	  *	If argument is a tag path, creates appropriate branches and tags
48	  *	If argument is a simple string then sets that as a root tag name
49	  *	@method		XML
50	  *	@param		optional string argument
51	  *	@returns	none
52	  */
53	function XML($argument = "") {
54		$success = false;
55		$this->Tree();
56		$this->pathSeparator = "/";
57		$this->tag = new Tag();
58		if(is_string($argument)) {
59			// if this is an XML string to be parsed
60			if(strpos($argument, $this->tag->tagEndOpen) > 0 || strpos($argument, $this->tag->tagClose) > 0)
61				$this->parseFromString($argument);
62			// else if this is a tag path to be created
63			elseif(strpos($argument, $this->pathSeparator) > 0) {
64				$tags = explode($this->pathSeparator, $argument);
65				$this->tag->setTagName($tags[0]);
66				$this->setTagContent("", $argument);
67			}
68			else
69				$this->tag->setTagName($argument);
70			$success = true;
71		}
72		else
73			$success = false;
74		return $success;
75	}
76
77	/**
78	  *	Adds another XML tree as a branch to the current XML object
79	  *	@method		addXMLAsBranch
80	  *	@param		object xml
81	  *	@param		optional mixed id
82	  *	@returns	true if successful, false otherwise
83	  */
84	function addXMLAsBranch($xml, $id = -1) {
85		$success = false;
86		if(is_object($xml) && strtolower(get_class($xml)) == "xml") {
87			$newBranch = new XMLBranch();
88			$newBranch->nodes = $xml->nodes;
89			$newBranch->tag = $xml->tag;
90			$success = $this->addXMLBranch($newBranch, $id);
91		}
92		return $success;
93	}
94
95	/**
96	  *	Adds XML Branch to the current XML object
97	  *	@method		addXMLBranch
98	  *	@param		object xmlBranch
99	  *	@param		optional mixed id
100	  *	@returns	true if successful, false otherwise
101	  */
102	function addXMLBranch($xmlBranch, $id = -1) {
103		$success = false;
104		if(is_object($xmlBranch) && strtolower(get_class($xmlBranch)) == "xmlbranch") {
105			$xmlBranch->setParentXML($this);
106			$success = $this->addNode($id, $xmlBranch);
107		}
108		return $success;
109	}
110
111	/**
112	  *	Adds XML Leaf to the current XML object
113	  *	@method		addXMLLeaf
114	  *	@param		object xmlLeaf
115	  *	@param		optional mixed id
116	  *	@returns	true if successful, false otherwise
117	  */
118	function addXMLLeaf($xmlLeaf, $id = -1) {
119		$success = false;
120		if(is_object($xmlLeaf) && strtolower(get_class($xmlLeaf)) == "xmlleaf") {
121			$xmlLeaf->setParentXML($this);
122			$success = $this->addNode($id, $xmlLeaf);
123		}
124		return $success;
125	}
126
127	/**
128	  *	Retrieves an array of references to XMLBranches within the specified path, tag name, attribute name, and attribute value
129	  *	@method		getBranches
130	  *	@param		optional string tagPath
131	  *	@param		optional string tagName
132	  *	@param		optional string attrName
133	  *	@param		optional string attrValue
134	  *	@returns	array of references to XMLBranch objects that meet specified criteria, or false if none found
135	  */
136	function getBranches($tagPath = "", $tagName = "", $attrName = "", $attrValue = "") {
137		$branchArray = array();
138		if($tagPath == "")
139			$tagPath = $this->tag->getTagName();
140		$tags = explode($this->pathSeparator, $tagPath);
141		if($this->tag->getTagName() == $tags[0]) {
142			if(count($tags) == 1) {
143				$arrKeys = array_keys($this->nodes);
144				for($index = 0; $index < count($arrKeys); $index ++) {
145					if(gettype($this->nodes[$arrKeys[$index]]) == "object" && strtolower(get_class($this->nodes[$arrKeys[$index]])) == "xmlbranch") {
146						if(($tagName == "" || $this->nodes[$arrKeys[$index]]->tag->getTagName() == $tagName) &&
147							($attrName == "" || $this->nodes[$arrKeys[$index]]->tag->attributeExists($attrName)) &&
148							($attrValue == "" || $this->nodes[$arrKeys[$index]]->tag->getTagAttribute($attrName) == $attrValue)) {
149							$branchArray[] = &$this->nodes[$arrKeys[$index]];
150						}
151					}
152				}
153			}
154			else {
155				$arrKeys = array_keys($this->nodes);
156				for($index = 0; $index < count($arrKeys); $index ++) {
157					if(gettype($this->nodes[$arrKeys[$index]]) == "object" && strtolower(get_class($this->nodes[$arrKeys[$index]])) == "xmlbranch") {
158						if($this->nodes[$arrKeys[$index]]->tag->getTagName() == $tags[1]) {
159							$newTagPath = implode($this->pathSeparator, array_slice($tags, 1));
160							$newArray = $this->nodes[$arrKeys[$index]]->getBranches($newTagPath, $tagName, $attrName, $attrValue);
161							if($newArray !== false)
162								$branchArray = array_merge($branchArray, $newArray);
163						}
164					}
165				}
166			}
167		}
168		if(count($branchArray) == 0)
169			$branchArray = false;
170		return $branchArray;
171	}
172
173	/**
174	  *	Retrieves an array of references to XMLLeaf(s) within the specified path
175	  *	@method		getLeafs
176	  *	@param		optional string tagPath
177	  *	@returns	array of references to XMLLeaf objects in specified tag path, false if none found
178	  */
179	function getLeafs($tagPath = "") {
180		$leafArray = array();
181		if($tagPath == "")
182			$tagPath = $this->tag->getTagName();
183		$tags = explode($this->pathSeparator, $tagPath);
184		if($this->tag->getTagName() == $tags[0]) {
185			if(count($tags) == 1) {
186				$arrKeys = array_keys($this->nodes);
187				for($index = 0; $index < count($arrKeys); $index ++) {
188					if(gettype($this->nodes[$arrKeys[$index]]) == "object" && strtolower(get_class($this->nodes[$arrKeys[$index]])) == "xmlleaf") {
189						$leafArray[] = &$this->nodes[$arrKeys[$index]];
190					}
191				}
192			}
193			else {
194				$arrKeys = array_keys($this->nodes);
195				for($index = 0; $index < count($arrKeys); $index ++) {
196					if(gettype($this->nodes[$arrKeys[$index]]) == "object" && strtolower(get_class($this->nodes[$arrKeys[$index]])) == "xmlbranch") {
197						if($this->nodes[$arrKeys[$index]]->tag->getTagName() == $tags[1]) {
198							$newTagPath = implode($this->pathSeparator, array_slice($tags, 1));
199							$newArray = $this->nodes[$arrKeys[$index]]->getLeafs($newTagPath);
200							if($newArray !== false)
201								$leafArray = array_merge($leafArray, $newArray);
202						}
203					}
204				}
205			}
206		}
207		if(count($leafArray) == 0)
208			$leafArray = false;
209		return $leafArray;
210	}
211
212	/**
213	  *	Returns attribute value of the specified tag and tagpath
214	  *	@method		getTagAttribute
215	  *	@param		string attributeName
216	  *	@param		optional string tagPath
217	  *	@returns	attribute of the specified tag if successful, false otherwise
218	  */
219	function getTagAttribute($attributeName, $tagPath = "") {
220		if($tagPath == "")
221			$tagPath = $this->tag->getTagName();
222		$tags = explode($this->pathSeparator, $tagPath);
223		$attributeValue = false;
224		if($this->tag->getTagName() == $tags[0]) {
225			if(sizeof($tags) == 1) {
226				if($this->tag->attributeExists($attributeName))
227					$attributeValue = $this->tag->getTagAttribute($attributeName);
228			}
229			else {
230				foreach($this->nodes as $node) {
231					if(strtolower(get_class($node)) == "xmlbranch")
232						if($node->tag->getTagName() == $tags[1]) {
233							$newTagPath = implode($this->pathSeparator, array_slice($tags, 1));
234							$attributeValue = $node->getTagAttribute($attributeName, $newTagPath);
235						}
236				}
237			}
238		}
239		return $attributeValue;
240	}
241
242	/**
243	  *	Returns contents of the specified tag path
244	  *	@method		getTagContent
245	  *	@param		optional string tagPath
246	  *	@returns	content of the tag from the specified path if successful, false otherwise
247	  */
248	function getTagContent($tagPath = "") {
249		if($tagPath == "")
250			$tagPath = $this->tag->getTagName();
251		$tags = explode($this->pathSeparator, $tagPath);
252		$tagValue = false;
253		if($this->tag->getTagName() == $tags[0]) {
254			if(sizeof($tags) == 1)
255				$tagValue = $this->getXMLContent();
256			else {
257				foreach($this->nodes as $node) {
258					if(strtolower(get_class($node)) == "xmlbranch")
259						if($node->tag->getTagName() == $tags[1]) {
260							$newTagPath = implode($this->pathSeparator, array_slice($tags, 1));
261							$tagValue = $node->getTagContent($newTagPath);
262						}
263				}
264			}
265		}
266		return $tagValue;
267	}
268
269	/**
270	  *	Retrieves the tag name of the current object
271	  *	@method		getTagName
272	  *	@returns	tag name
273	  */
274	function getTagName() {
275		return($this->tag->getTagName());
276	}
277
278	/**
279	  *	Gets contents from the current object
280	  *	@method		getXMLContent
281	  *	@returns	contents of the current XML tag
282	  */
283	function getXMLContent() {
284		$xmlContent = "";
285		foreach($this->nodes as $node) {
286			if(gettype($node) == "object") {
287				if(strtolower(get_class($node)) == "xmlbranch")
288					$xmlContent .= $node->getXMLString();
289				elseif(strtolower(get_class($node)) == "xmlleaf")
290					$xmlContent .= $node->getValue();
291			}
292		}
293		return $xmlContent;
294	}
295
296	/**
297	  *	Gets the whole XML string of the current object
298	  *	@method		getXMLString
299	  *	@param		optional mixed indent
300	  *	@returns	complete XML string of current object
301	  */
302	function getXMLString($indent = false) {
303		$xmlString = "";
304		$containsBranches = false;
305		$containsLeafs = false;
306		$newIndent = false;
307		if($indent === false)
308			$newIndent = false;
309		else {
310			$newIndent = $indent + 1;
311			$this->tag->setTagFormat($this->tag->FORMAT_INDENT, $indent);
312		}
313		foreach($this->nodes as $node) {
314			if(gettype($node) == "object") {
315				if(strtolower(get_class($node)) == "xmlbranch") {
316					$this->tag->tagContent .= $node->getXMLString($newIndent);
317					$containsBranches = true;
318				}
319				elseif(strtolower(get_class($node)) == "xmlleaf") {
320					$this->tag->tagContent .= $node->getValue();
321					$containsLeafs = true;
322				}
323			}
324		}
325		if($containsBranches)
326			$this->tag->setTagFormatEndTag(true);
327		$xmlString = $this->tag->getTagString();
328		$this->tag->setTagContent("");
329		return $xmlString;
330	}
331
332	/**
333	  *	Find out whether the current object has any branches
334	  *	@method		hasBranch
335	  *	@returns	true if branches exist, false otherwise
336	  */
337	function hasBranch() {
338		$hasBranch = false;
339		foreach($this->nodes as $node) {
340			if(strtolower(get_class($node)) == "xmlbranch") {
341				$hasBranch = true;
342				break;
343			}
344		}
345		return $hasBranch;
346	}
347
348	/**
349	  *	Find out whether the current object has any leaf(s)
350	  *	@method		hasLeaf
351	  *	@returns	true if leaf(s) exist, false otherwise
352	  */
353	function hasLeaf() {
354		$hasLeaf = false;
355		foreach($this->nodes as $node) {
356			if(strtolower(get_class($node)) == "xmlleaf") {
357				$hasLeaf = true;
358				break;
359			}
360		}
361		return $hasLeaf;
362	}
363
364	/**
365	  *	Parse entire XML string into the current object; also called from constructor
366	  *	@method		parseFromString
367	  *	@param		string parseString
368	  *	@returns	none
369	  */
370	function parseFromString($parseString) {
371		$tagResult = $this->tag->setTagFromString($parseString);
372		if($tagResult !== false) {
373			$this->parseNodesFromTag();
374			$this->tag->setTagContent("");
375		}
376	}
377
378	/**
379	  *	Parses the current tag content into Branches and Leaf(s); called from parseFromString
380	  *	@method		parseNodesFromTag
381	  *	@returns	none
382	  */
383	function parseNodesFromTag() {
384		$tempTag = new Tag();
385		$parseString = $this->tag->getTagContent();
386		while($tagParsed = $tempTag->setTagFromString($parseString)) {
387			if($tagParsed[0] != 0 && substr($parseString, 0, $tagParsed[0]) != "")
388				$this->addXMLLeaf(new XMLLeaf(substr($parseString, 0, $tagParsed[0])));
389			$branch = new XMLBranch();
390			$tempTagCopy = new Tag();
391			$tempTagCopy->setTagName($tempTag->getTagName());
392			$tempTagCopy->tagAttributes = $tempTag->tagAttributes;
393			$tempTagCopy->setTagContent($tempTag->getTagContent());
394			$branch->setTag($tempTagCopy);
395			$branch->parseNodesFromTag();
396			$branch->tag->setTagContent("");
397			$this->addXMLBranch($branch);
398			$parseString = substr($parseString, $tagParsed[1]);
399		}
400		if(strlen($parseString) > 0 && $parseString != "")
401			$this->addXMLLeaf(new XMLLeaf($parseString));
402	}
403
404	/**
405	  *	Removes all Branches from current object
406	  *	@method		removeAllBranches
407	  */
408	function removeAllBranches() {
409		foreach($this->nodes as $key => $value) {
410			if(strtolower(get_class($value)) == "xmlbranch")
411				unset($this->nodes[$key]);
412		}
413	}
414
415	/**
416	  *	Removes all Leaf(s) from current object
417	  *	@method		removeAllLeafs
418	  */
419	function removeAllLeafs() {
420		foreach($this->nodes as $key => $value) {
421			if(strtolower(get_class($value)) == "xmlleaf")
422				unset($this->nodes[$key]);
423		}
424	}
425
426	/**
427	  *	Removes Branches with the specified criteria
428	  *	@method		removeBranches
429	  *	@param		optional string tagPath
430	  *	@param		optional string tagName
431	  *	@param		optional string attrName
432	  *	@param		optional string attrValue
433	  *	@returns	number of branches deleted
434	  */
435	function removeBranches($tagPath = "", $tagName = "", $attrName = "", $attrValue = "") {
436		$branchesDeleted = 0;
437		$referencedBranches = array();
438		$tags = explode($this->pathSeparator, $tagPath);
439		if(count($tags) > 1) {
440			$parentTagName = array_pop($tags);
441			$parentTagPath = implode($this->pathSeparator, $tags);
442			$referencedBranches = $this->getBranches($parentTagPath, $parentTagName);
443		}
444		else {
445			$referencedBranches[] = &$this;
446		}
447		for($i = 0; $i < count($referencedBranches); $i ++) {
448			$arrKeys = array_keys($referencedBranches[$i]->nodes);
449			for($index = 0; $index < count($arrKeys); $index ++) {
450				if(gettype($referencedBranches[$i]->nodes[$arrKeys[$index]]) == "object" && strtolower(get_class($referencedBranches[$i]->nodes[$arrKeys[$index]])) == "xmlbranch") {
451					if(($tagName == "" || $referencedBranches[$i]->nodes[$arrKeys[$index]]->tag->getTagName() == $tagName) &&
452						($attrName == "" || $referencedBranches[$i]->nodes[$arrKeys[$index]]->tag->attributeExists($attrName)) &&
453						($attrValue == "" || $referencedBranches[$i]->nodes[$arrKeys[$index]]->tag->getTagAttribute($attrName) == $attrValue)) {
454						$referencedBranches[$i]->removeNode($arrKeys[$index]);
455						$branchesDeleted ++;
456					}
457				}
458			}
459		}
460		return $branchesDeleted;
461	}
462
463	/**
464	  *	Sets tag object of a branch specified by branch ID for the current object; see getBranches and setTag
465	  *	@method		setBranchTag
466	  *	@param		mixed branchId
467	  *	@param		object tag
468	  *	@returns	true on success, false otherwise
469	  */
470	function setBranchTag($branchId, $tag) {
471		$success = true;
472		if(strtolower(get_class($this->nodes[$branchId])) == "xmlbranch" && strtolower(get_class($tag)) == "tag")
473			$this->nodes[$branchId]->setTag($tag);
474		else
475			$success = false;
476		return $success;
477	}
478
479	/**
480	  *	Sets tag object of the current object
481	  *	@method		setTag
482	  *	@param		object tag
483	  *	@returns	true if successful, false otherwise
484	  */
485	function setTag($tag) {
486		$success = true;
487		if(strtolower(get_class($tag)) == "tag")
488			$this->tag = $tag;
489		else
490			$success = false;
491		return $success;
492	}
493
494	/**
495	  *	Sets an attribute name and value on an existing tag found via tagpath string
496	  *	@method		setTagAttribute
497	  *	@param		string attributeName
498	  *	@param		optional string attributeValue
499	  *	@param		optional string tagPath
500	  *	@returns	true if successful, false otherwise
501	  */
502	function setTagAttribute($attributeName, $attributeValue = "", $tagPath = "") {
503		if($tagPath == "")
504			$tagPath = $this->tag->getTagName();
505		$success = true;
506		$tags = explode($this->pathSeparator, $tagPath);
507		if($this->tag->getTagName() == $tags[0]) {
508			if(sizeof($tags) == 1)
509				$this->tag->setAttribute($attributeName, $attributeValue);
510			else {
511				$nodeTagFound = false;
512				reset($this->nodes);
513				$arrKeys = array_keys($this->nodes);
514				for($index = 0; $index < count($arrKeys); $index ++) {
515					$node =& $this->nodes[$arrKeys[$index]];
516					if(strtolower(get_class($node)) == "xmlbranch")
517						if($node->tag->getTagName() == $tags[1]) {
518							$newTagPath = implode($this->pathSeparator, array_slice($tags, 1));
519							$success = $node->setTagAttribute($attributeName, $attributeValue, $newTagPath);
520							$nodeTagFound = true;
521						}
522				}
523				if(!$nodeTagFound)
524					$success = false;
525			}
526		}
527		else
528			$success = false;
529		return $success;
530	}
531
532	/**
533	  *	Sets content of the specified tag
534	  *	@method		setTagContent
535	  *	@param		mixed content
536	  *	@param		optional string tagPath
537	  *	@returns	true if successful, false otherwise
538	  */
539	function setTagContent($content, $tagPath = "") {
540		if($tagPath == "")
541			$tagPath = $this->tag->getTagName();
542		$success = true;
543		$tags = explode($this->pathSeparator, $tagPath);
544		if($this->tag->getTagName() == $tags[0]) {
545			if(sizeof($tags) == 1) {
546				//$this->nodes = array(new XMLLeaf($content));
547				$this->removeAllNodes();
548				$this->addXMLLeaf(new XMLLeaf($content));
549			}
550			else {
551				$nodeTagFound = false;
552				reset($this->nodes);
553				$arrKeys = array_keys($this->nodes);
554				for($index = 0; $index < count($arrKeys); $index ++) {
555					$node =& $this->nodes[$arrKeys[$index]];
556					if(strtolower(get_class($node)) == "xmlbranch")
557						if($node->tag->getTagName() == $tags[1]) {
558							$newTagPath = implode($this->pathSeparator, array_slice($tags, 1));
559							$success = $node->setTagContent($content, $newTagPath);
560							$nodeTagFound = true;
561						}
562				}
563				if(!$nodeTagFound) {
564					$branch = new XMLBranch();
565					$branch->setTag(new Tag($tags[1]));
566					$newTagPath = implode($this->pathSeparator, array_slice($tags, 1));
567					$branch->setTagContent($content, $newTagPath);
568					$this->addXMLBranch($branch);
569				}
570			}
571		}
572		return $success;
573	}
574
575}
576
577import("org.active-link.xml.XMLBranch");
578import("org.active-link.xml.XMLLeaf");
579
580?>
581