PageRenderTime 51ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/core/classes/framework/xmlwrapper/xmlwrapper.php

https://github.com/GinoPane/fantasia
PHP | 277 lines | 169 code | 41 blank | 67 comment | 35 complexity | f33d00afececdaf2bbe09d292f556cc1 MD5 | raw file
  1. <?php
  2. /**
  3. *
  4. * Service class for XML handling, converts xml to arrays and arrays to xml
  5. * @see XmlWrapper::arrayToXml, XmlWrapper::xmlToArray
  6. *
  7. *
  8. * @author Sergey Karavay
  9. *
  10. */
  11. class XmlWrapper {
  12. private $_xml = null;
  13. private $_encoding = 'UTF-8';
  14. private $_options = array();
  15. private function __construct($options = array()) {
  16. $version = "1.0";
  17. $formatOutput = true;
  18. class_exists('cfg_app') ? $encoding = Cfg_App::DEFAULT_ENCODING : $encoding = 'utf-8';
  19. extract($options, EXTR_IF_EXISTS | EXTR_OVERWRITE);
  20. $this->_xml = new DomDocument($version, $encoding);
  21. $this->_options = $options;
  22. $this->_xml->formatOutput = $formatOutput;
  23. $this->_encoding = $encoding;
  24. }
  25. /**
  26. *
  27. *
  28. * @param array $options Options for parser;
  29. * - 'version'
  30. * - 'formatOutput'
  31. * - 'encoding'
  32. * - 'numericKeysName'
  33. *
  34. * @return XmlWrapper
  35. */
  36. public static function getParser($options = array())
  37. {
  38. return new XmlWrapper($options);
  39. }
  40. /**
  41. * Converts an Array to XML
  42. *
  43. * @param array $data array to be converted
  44. * @param array $options array of options. Will be merged with existing.
  45. *
  46. * @see XmlWrapper::getParser() for more
  47. * @return DomDocument
  48. */
  49. public function arrayToXml(array $data, $options = array()) {
  50. $xml = $this->_getXMLRoot();
  51. $rootNodeName = 'root';
  52. extract($options, EXTR_IF_EXISTS | EXTR_OVERWRITE);
  53. $this->_options = array_merge($this->_options, $options);
  54. $xml->appendChild($this->_convertArrayToXml($rootNodeName, $data));
  55. return $xml->saveXML();
  56. }
  57. /**
  58. *
  59. * Converts xml string to array
  60. *
  61. * @param mixed $inputXml DOMDocument instance or a valid xml string
  62. * @return array converted array
  63. */
  64. public function xmlToArray($inputXml) {
  65. $xml = $this->_getXMLRoot();
  66. $error = false;
  67. if (is_string($inputXml)) {
  68. if (!$xml->loadXML($inputXml)) {
  69. trigger_error('Error parsing the XML string.', E_USER_WARNING);
  70. $error = true;
  71. }
  72. } else {
  73. if (is_a($inputXml, 'DOMDocument')) {
  74. trigger_error('The input XML object should be descendant of DOMDocument', E_USER_WARNING);
  75. $error = true;
  76. }
  77. $xml = $this->_xml = $inputXml;
  78. }
  79. if (!$error)
  80. {
  81. $output = array();
  82. $output[$xml->documentElement->tagName] =
  83. $this->_convertXmlToArray($xml->documentElement);
  84. return $output;
  85. }
  86. else
  87. {
  88. return null;
  89. }
  90. }
  91. /**
  92. * Convert an Array to XML
  93. * @param string $node_name - name of the root node to be converted
  94. * @param array $arr - aray to be converterd
  95. * @return DOMNode
  96. */
  97. private function _convertArrayToXml($nodeName, $arr = array())
  98. {
  99. $node = $this->_xml->createElement($nodeName);
  100. if (is_array($arr)) {
  101. if (isset($arr['@attributes'])) {
  102. foreach ($arr['@attributes'] as $key => $value) {
  103. if (!$this->_isValidTagName($key)) {
  104. trigger_error("Illegal attribute name: \"{$key}\" in node \"{$nodeName}\"");
  105. }
  106. $node->setAttribute($key, self::_valueToString($value));
  107. }
  108. unset($arr['@attributes']);
  109. }
  110. // check if it has a value stored in @value, if yes store the value and return
  111. // else check if its directly stored as string
  112. if (isset($arr['@value'])) {
  113. $node->appendChild($this->_xml->createTextNode($this->_valueToString($arr['@value'])));
  114. unset($arr['@value']);
  115. return $node;
  116. } else if (isset($arr['@cdata'])) {
  117. $node->appendChild($this->_xml->createCDATASection($this->_valueToString($arr['@cdata'])));
  118. unset($arr['@cdata']);
  119. return $node;
  120. }
  121. }
  122. if (is_array($arr)) {
  123. foreach ($arr as $key => $value) {
  124. if (!$this->_isValidTagName($key)) {
  125. trigger_error("Illegal tag name: \"{$key}\" in node \"{$nodeName}\"");
  126. }
  127. if (is_array($value) && is_numeric(key($value))) {
  128. $numericKeyName = isset($this->_options['numericKeysName']) ? $this->_options['numericKeysName'] : $key;
  129. foreach ($value as $subValue) {
  130. $node->appendChild($this->_convertArrayToXml(
  131. $numericKeyName,
  132. $subValue
  133. ));
  134. }
  135. } else {
  136. $node->appendChild($this->_convertArrayToXml($key, $value));
  137. }
  138. unset($arr[$key]);
  139. }
  140. }
  141. if (!is_array($arr)) {
  142. $node->appendChild($this->_xml->createTextNode($this->_valueToString($arr)));
  143. }
  144. return $node;
  145. }
  146. /**
  147. * Convert an XML to array
  148. *
  149. * @param DOMNode $node
  150. * @return array
  151. */
  152. private function _convertXmlToArray(DOMNode $node) {
  153. $output = array();
  154. switch ($node->nodeType) {
  155. case XML_CDATA_SECTION_NODE:
  156. $output['@cdata'] = trim($node->textContent);
  157. break;
  158. case XML_TEXT_NODE:
  159. $output = trim($node->textContent);
  160. break;
  161. case XML_ELEMENT_NODE:
  162. // for each child node, call the covert function recursively
  163. for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++) {
  164. $child = $node->childNodes->item($i);
  165. $v = $this->_convertXmlToArray($child);
  166. if (isset($child->tagName)) {
  167. $t = $child->tagName;
  168. // assume more nodes of same kind are coming
  169. if (!isset($output[$t])) {
  170. $output[$t] = array();
  171. }
  172. $output[$t][] = $v;
  173. } else {
  174. //check if it is not an empty text node
  175. if ($v !== '') {
  176. $output = $v;
  177. }
  178. }
  179. }
  180. if (is_array($output)) {
  181. // if only one node of its kind, assign it directly instead if array($value);
  182. foreach ($output as $t => $v) {
  183. if (is_array($v) && count($v) == 1) {
  184. $output[$t] = $v[0];
  185. }
  186. }
  187. if (empty($output)) {
  188. //for empty nodes
  189. $output = '';
  190. }
  191. }
  192. // loop through the attributes and collect them
  193. if ($node->attributes->length) {
  194. $a = array();
  195. foreach ($node->attributes as $attrName => $attrNode) {
  196. $a[$attrName] = (string) $attrNode->value;
  197. }
  198. // if its an leaf node, store the value in @value instead of directly storing it.
  199. if (!is_array($output)) {
  200. $output = array('@value' => $output);
  201. }
  202. $output['@attributes'] = $a;
  203. }
  204. break;
  205. }
  206. return $output;
  207. }
  208. private function _getXMLRoot() {
  209. if (!$this->_xml) {
  210. $this->_xml = new DOMDocument();
  211. }
  212. return $this->_xml;
  213. }
  214. /*
  215. * Get string representation of the value
  216. */
  217. private function _valueToString($value) {
  218. if (!is_bool($value)) {
  219. return (string)$value;
  220. } else {
  221. return $value ? 'true' : 'false';
  222. }
  223. }
  224. /**
  225. * Check if the tag name or attribute name contains illegal characters
  226. * @link http://www.w3.org/TR/xml/#sec-common-syn
  227. *
  228. * @param string $tag
  229. * @return bool
  230. */
  231. private function _isValidTagName($tag) {
  232. $matches = array();
  233. $pattern = '/^[a-z_]+[a-z0-9\:\-\.\_]*[^:]*$/i';
  234. return preg_match($pattern, $tag, $matches) && $matches[0] == $tag;
  235. }
  236. };