PageRenderTime 39ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/install/includes/classes/XMLParser5.php

https://github.com/vaughnpaul/NOS
PHP | 430 lines | 140 code | 60 blank | 230 comment | 14 complexity | 06ec5ceb4e01480383c7c238343ba4d9 MD5 | raw file
  1. <?php
  2. /**
  3. This program is free software; you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation; either version 2 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. For support, please visit http://www.criticaldevelopment.net/xml/
  12. */
  13. /**
  14. * XML Parser Class (php5)
  15. *
  16. * Parses an XML document into an object structure much like the SimpleXML extension.
  17. *
  18. * @author Adam A. Flynn <adamaflynn@criticaldevelopment.net>
  19. * @copyright Copyright (c) 2005-2007, Adam A. Flynn
  20. *
  21. * @version 1.3.0
  22. */
  23. class XMLParser
  24. {
  25. /**
  26. * The XML parser
  27. *
  28. * @var resource
  29. */
  30. private $parser;
  31. /**
  32. * The XML document
  33. *
  34. * @var string
  35. */
  36. private $xml;
  37. /**
  38. * Document tag
  39. *
  40. * @var object
  41. */
  42. public $document;
  43. /**
  44. * Current object depth
  45. *
  46. * @var array
  47. */
  48. private $stack;
  49. /**
  50. * Whether or not to replace dashes and colons in tag
  51. * names with underscores.
  52. *
  53. * @var bool
  54. */
  55. private $cleanTagNames;
  56. /**
  57. * Constructor. Loads XML document.
  58. *
  59. * @param string $xml The string of the XML document
  60. * @return XMLParser
  61. */
  62. function __construct($xml = '', $cleanTagNames = true)
  63. {
  64. //Load XML document
  65. $this->xml = $xml;
  66. //Set stack to an array
  67. $this->stack = array();
  68. //Set whether or not to clean tag names
  69. $this->cleanTagNames = $cleanTagNames;
  70. }
  71. /**
  72. * Initiates and runs PHP's XML parser
  73. */
  74. public function Parse()
  75. {
  76. //Create the parser resource
  77. $this->parser = xml_parser_create();
  78. //Set the handlers
  79. xml_set_object($this->parser, $this);
  80. xml_set_element_handler($this->parser, 'StartElement', 'EndElement');
  81. xml_set_character_data_handler($this->parser, 'CharacterData');
  82. //Error handling
  83. if (!xml_parse($this->parser, $this->xml))
  84. $this->HandleError(xml_get_error_code($this->parser), xml_get_current_line_number($this->parser), xml_get_current_column_number($this->parser));
  85. //Free the parser
  86. xml_parser_free($this->parser);
  87. }
  88. /**
  89. * Handles an XML parsing error
  90. *
  91. * @param int $code XML Error Code
  92. * @param int $line Line on which the error happened
  93. * @param int $col Column on which the error happened
  94. */
  95. private function HandleError($code, $line, $col)
  96. {
  97. trigger_error('XML Parsing Error at '.$line.':'.$col.'. Error '.$code.': '.xml_error_string($code));
  98. }
  99. /**
  100. * Gets the XML output of the PHP structure within $this->document
  101. *
  102. * @return string
  103. */
  104. public function GenerateXML()
  105. {
  106. return $this->document->GetXML();
  107. }
  108. /**
  109. * Gets the reference to the current direct parent
  110. *
  111. * @return object
  112. */
  113. private function GetStackLocation()
  114. {
  115. //Returns the reference to the current direct parent
  116. return end($this->stack);
  117. }
  118. /**
  119. * Handler function for the start of a tag
  120. *
  121. * @param resource $parser
  122. * @param string $name
  123. * @param array $attrs
  124. */
  125. private function StartElement($parser, $name, $attrs = array())
  126. {
  127. //Make the name of the tag lower case
  128. $name = strtolower($name);
  129. //Check to see if tag is root-level
  130. if (count($this->stack) == 0)
  131. {
  132. //If so, set the document as the current tag
  133. $this->document = new XMLTag($name, $attrs);
  134. //And start out the stack with the document tag
  135. $this->stack = array(&$this->document);
  136. }
  137. //If it isn't root level, use the stack to find the parent
  138. else
  139. {
  140. //Get the reference to the current direct parent
  141. $parent = $this->GetStackLocation();
  142. $parent->AddChild($name, $attrs, count($this->stack), $this->cleanTagNames);
  143. //If the cleanTagName feature is on, clean the tag names
  144. if($this->cleanTagNames)
  145. $name = str_replace(array(':', '-'), '_', $name);
  146. //Update the stack
  147. $this->stack[] = end($parent->$name);
  148. }
  149. }
  150. /**
  151. * Handler function for the end of a tag
  152. *
  153. * @param resource $parser
  154. * @param string $name
  155. */
  156. private function EndElement($parser, $name)
  157. {
  158. //Update stack by removing the end value from it as the parent
  159. array_pop($this->stack);
  160. }
  161. /**
  162. * Handler function for the character data within a tag
  163. *
  164. * @param resource $parser
  165. * @param string $data
  166. */
  167. private function CharacterData($parser, $data)
  168. {
  169. //Get the reference to the current parent object
  170. $tag = $this->GetStackLocation();
  171. //Assign data to it
  172. $tag->tagData .= trim($data);
  173. }
  174. }
  175. /**
  176. * XML Tag Object (php5)
  177. *
  178. * This object stores all of the direct children of itself in the $children array. They are also stored by
  179. * type as arrays. So, if, for example, this tag had 2 <font> tags as children, there would be a class member
  180. * called $font created as an array. $font[0] would be the first font tag, and $font[1] would be the second.
  181. *
  182. * To loop through all of the direct children of this object, the $children member should be used.
  183. *
  184. * To loop through all of the direct children of a specific tag for this object, it is probably easier
  185. * to use the arrays of the specific tag names, as explained above.
  186. *
  187. * @author Adam A. Flynn <adamaflynn@criticaldevelopment.net>
  188. * @copyright Copyright (c) 2005-2007, Adam A. Flynn
  189. *
  190. * @version 1.3.0
  191. */
  192. class XMLTag
  193. {
  194. /**
  195. * Array with the attributes of this XML tag
  196. *
  197. * @var array
  198. */
  199. public $tagAttrs;
  200. /**
  201. * The name of the tag
  202. *
  203. * @var string
  204. */
  205. public $tagName;
  206. /**
  207. * The data the tag contains
  208. *
  209. * So, if the tag doesn't contain child tags, and just contains a string, it would go here
  210. *
  211. * @var stat
  212. */
  213. public $tagData;
  214. /**
  215. * Array of references to the objects of all direct children of this XML object
  216. *
  217. * @var array
  218. */
  219. public $tagChildren;
  220. /**
  221. * The number of parents this XML object has (number of levels from this tag to the root tag)
  222. *
  223. * Used presently only to set the number of tabs when outputting XML
  224. *
  225. * @var int
  226. */
  227. public $tagParents;
  228. /**
  229. * Constructor, sets up all the default values
  230. *
  231. * @param string $name
  232. * @param array $attrs
  233. * @param int $parents
  234. * @return XMLTag
  235. */
  236. function __construct($name, $attrs = array(), $parents = 0)
  237. {
  238. //Make the keys of the attr array lower case, and store the value
  239. $this->tagAttrs = array_change_key_case($attrs, CASE_LOWER);
  240. //Make the name lower case and store the value
  241. $this->tagName = strtolower($name);
  242. //Set the number of parents
  243. $this->tagParents = $parents;
  244. //Set the types for children and data
  245. $this->tagChildren = array();
  246. $this->tagData = '';
  247. }
  248. /**
  249. * Adds a direct child to this object
  250. *
  251. * @param string $name
  252. * @param array $attrs
  253. * @param int $parents
  254. * @param bool $cleanTagName
  255. */
  256. public function AddChild($name, $attrs, $parents, $cleanTagName = true)
  257. {
  258. //If the tag is a reserved name, output an error
  259. if(in_array($name, array('tagChildren', 'tagAttrs', 'tagParents', 'tagData', 'tagName')))
  260. {
  261. trigger_error('You have used a reserved name as the name of an XML tag. Please consult the documentation (http://www.criticaldevelopment.net/xml/) and rename the tag named "'.$name.'" to something other than a reserved name.', E_USER_ERROR);
  262. return;
  263. }
  264. //Create the child object itself
  265. $child = new XMLTag($name, $attrs, $parents);
  266. //If the cleanTagName feature is on, replace colons and dashes with underscores
  267. if($cleanTagName)
  268. $name = str_replace(array(':', '-'), '_', $name);
  269. //Toss up a notice if someone's trying to to use a colon or dash in a tag name
  270. elseif(strstr($name, ':') || strstr($name, '-'))
  271. trigger_error('Your tag named "'.$name.'" contains either a dash or a colon. Neither of these characters are friendly with PHP variable names, and, as such, you may have difficulty accessing them. You might want to think about enabling the cleanTagName feature (pass true as the second argument of the XMLParser constructor). For more details, see http://www.criticaldevelopment.net/xml/', E_USER_NOTICE);
  272. //If there is no array already set for the tag name being added,
  273. //create an empty array for it
  274. if(!isset($this->$name))
  275. $this->$name = array();
  276. //Add the reference of it to the end of an array member named for the tag's name
  277. $this->{$name}[] = &$child;
  278. //Add the reference to the children array member
  279. $this->tagChildren[] = &$child;
  280. //Return a reference to this object for the stack
  281. return $this;
  282. }
  283. /**
  284. * Returns the string of the XML document which would be generated from this object
  285. *
  286. * This function works recursively, so it gets the XML of itself and all of its children, which
  287. * in turn gets the XML of all their children, which in turn gets the XML of all thier children,
  288. * and so on. So, if you call GetXML from the document root object, it will return a string for
  289. * the XML of the entire document.
  290. *
  291. * This function does not, however, return a DTD or an XML version/encoding tag. That should be
  292. * handled by XMLParser::GetXML()
  293. *
  294. * @return string
  295. */
  296. public function GetXML()
  297. {
  298. //Start a new line, indent by the number indicated in $this->parents, add a <, and add the name of the tag
  299. $out = "\n".str_repeat("\t", $this->tagParents).'<'.$this->tagName;
  300. //For each attribute, add attr="value"
  301. foreach($this->tagAttrs as $attr => $value)
  302. $out .= ' '.$attr.'="'.$value.'"';
  303. //If there are no children and it contains no data, end it off with a />
  304. if(empty($this->tagChildren) && empty($this->tagData))
  305. $out .= " />";
  306. //Otherwise...
  307. else
  308. {
  309. //If there are children
  310. if(!empty($this->tagChildren))
  311. {
  312. //Close off the start tag
  313. $out .= '>';
  314. //For each child, call the GetXML function (this will ensure that all children are added recursively)
  315. foreach($this->tagChildren as $child)
  316. $out .= $child->GetXML();
  317. //Add the newline and indentation to go along with the close tag
  318. $out .= "\n".str_repeat("\t", $this->tagParents);
  319. }
  320. //If there is data, close off the start tag and add the data
  321. elseif(!empty($this->tagData))
  322. $out .= '>'.$this->tagData;
  323. //Add the end tag
  324. $out .= '</'.$this->tagName.'>';
  325. }
  326. //Return the final output
  327. return $out;
  328. }
  329. /**
  330. * Deletes this tag's child with a name of $childName and an index
  331. * of $childIndex
  332. *
  333. * @param string $childName
  334. * @param int $childIndex
  335. */
  336. public function Delete($childName, $childIndex = 0)
  337. {
  338. //Delete all of the children of that child
  339. $this->{$childName}[$childIndex]->DeleteChildren();
  340. //Destroy the child's value
  341. $this->{$childName}[$childIndex] = null;
  342. //Remove the child's name from the named array
  343. unset($this->{$childName}[$childIndex]);
  344. //Loop through the tagChildren array and remove any null
  345. //values left behind from the above operation
  346. for($x = 0; $x < count($this->tagChildren); $x ++)
  347. {
  348. if(is_null($this->tagChildren[$x]))
  349. unset($this->tagChildren[$x]);
  350. }
  351. }
  352. /**
  353. * Removes all of the children of this tag in both name and value
  354. */
  355. private function DeleteChildren()
  356. {
  357. //Loop through all child tags
  358. for($x = 0; $x < count($this->tagChildren); $x ++)
  359. {
  360. //Do this recursively
  361. $this->tagChildren[$x]->DeleteChildren();
  362. //Delete the name and value
  363. $this->tagChildren[$x] = null;
  364. unset($this->tagChildren[$x]);
  365. }
  366. }
  367. }
  368. ?>