Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

353 lines
12 KiB

  1. <?php
  2. /**
  3. * @file include/xml.php
  4. */
  5. /**
  6. * @brief This class contain functions to work with XML data
  7. *
  8. */
  9. class xml {
  10. /**
  11. * @brief Creates an XML structure out of a given array
  12. *
  13. * @param array $array The array of the XML structure that will be generated
  14. * @param object $xml The createdXML will be returned by reference
  15. * @param bool $remove_header Should the XML header be removed or not?
  16. * @param array $namespaces List of namespaces
  17. * @param bool $root - interally used parameter. Mustn't be used from outside.
  18. *
  19. * @return string The created XML
  20. */
  21. public static function from_array($array, &$xml, $remove_header = false, $namespaces = array(), $root = true) {
  22. if ($root) {
  23. foreach($array as $key => $value) {
  24. foreach ($namespaces AS $nskey => $nsvalue)
  25. $key .= " xmlns".($nskey == "" ? "":":").$nskey.'="'.$nsvalue.'"';
  26. $root = new SimpleXMLElement("<".$key."/>");
  27. self::from_array($value, $root, $remove_header, $namespaces, false);
  28. $dom = dom_import_simplexml($root)->ownerDocument;
  29. $dom->formatOutput = true;
  30. $xml = $dom;
  31. $xml_text = $dom->saveXML();
  32. if ($remove_header)
  33. $xml_text = trim(substr($xml_text, 21));
  34. return $xml_text;
  35. }
  36. }
  37. foreach($array as $key => $value) {
  38. if ($key == "@attributes") {
  39. if (!isset($element) OR !is_array($value))
  40. continue;
  41. foreach ($value as $attr_key => $attr_value) {
  42. $element_parts = explode(":", $attr_key);
  43. if ((count($element_parts) > 1) AND isset($namespaces[$element_parts[0]]))
  44. $namespace = $namespaces[$element_parts[0]];
  45. else
  46. $namespace = NULL;
  47. $element->addAttribute ($attr_key, $attr_value, $namespace);
  48. }
  49. continue;
  50. }
  51. $element_parts = explode(":", $key);
  52. if ((count($element_parts) > 1) AND isset($namespaces[$element_parts[0]]))
  53. $namespace = $namespaces[$element_parts[0]];
  54. else
  55. $namespace = NULL;
  56. if (!is_array($value))
  57. $element = $xml->addChild($key, xmlify($value), $namespace);
  58. elseif (is_array($value)) {
  59. $element = $xml->addChild($key, NULL, $namespace);
  60. self::from_array($value, $element, $remove_header, $namespaces, false);
  61. }
  62. }
  63. }
  64. /**
  65. * @brief Copies an XML object
  66. *
  67. * @param object $source The XML source
  68. * @param object $target The XML target
  69. * @param string $elementname Name of the XML element of the target
  70. */
  71. public static function copy(&$source, &$target, $elementname) {
  72. if (count($source->children()) == 0)
  73. $target->addChild($elementname, xmlify($source));
  74. else {
  75. $child = $target->addChild($elementname);
  76. foreach ($source->children() AS $childfield => $childentry)
  77. self::copy($childentry, $child, $childfield);
  78. }
  79. }
  80. /**
  81. * @brief Create an XML element
  82. *
  83. * @param object $doc XML root
  84. * @param string $element XML element name
  85. * @param string $value XML value
  86. * @param array $attributes array containing the attributes
  87. *
  88. * @return object XML element object
  89. */
  90. public static function create_element($doc, $element, $value = "", $attributes = array()) {
  91. $element = $doc->createElement($element, xmlify($value));
  92. foreach ($attributes AS $key => $value) {
  93. $attribute = $doc->createAttribute($key);
  94. $attribute->value = xmlify($value);
  95. $element->appendChild($attribute);
  96. }
  97. return $element;
  98. }
  99. /**
  100. * @brief Create an XML and append it to the parent object
  101. *
  102. * @param object $doc XML root
  103. * @param object $parent parent object
  104. * @param string $element XML element name
  105. * @param string $value XML value
  106. * @param array $attributes array containing the attributes
  107. */
  108. public static function add_element($doc, $parent, $element, $value = "", $attributes = array()) {
  109. $element = self::create_element($doc, $element, $value, $attributes);
  110. $parent->appendChild($element);
  111. }
  112. /**
  113. * @brief Convert an XML document to a normalised, case-corrected array
  114. * used by webfinger
  115. *
  116. * @param object $xml_element The XML document
  117. * @param integer $recursion_depth recursion counter for internal use - default 0
  118. * internal use, recursion counter
  119. *
  120. * @return array | sring The array from the xml element or the string
  121. */
  122. public static function element_to_array($xml_element, &$recursion_depth=0) {
  123. // If we're getting too deep, bail out
  124. if ($recursion_depth > 512) {
  125. return(null);
  126. }
  127. if (!is_string($xml_element) &&
  128. !is_array($xml_element) &&
  129. (get_class($xml_element) == 'SimpleXMLElement')) {
  130. $xml_element_copy = $xml_element;
  131. $xml_element = get_object_vars($xml_element);
  132. }
  133. if (is_array($xml_element)) {
  134. $result_array = array();
  135. if (count($xml_element) <= 0) {
  136. return (trim(strval($xml_element_copy)));
  137. }
  138. foreach($xml_element as $key=>$value) {
  139. $recursion_depth++;
  140. $result_array[strtolower($key)] =
  141. self::convert_element_to_array($value, $recursion_depth);
  142. $recursion_depth--;
  143. }
  144. if ($recursion_depth == 0) {
  145. $temp_array = $result_array;
  146. $result_array = array(
  147. strtolower($xml_element_copy->getName()) => $temp_array,
  148. );
  149. }
  150. return ($result_array);
  151. } else {
  152. return (trim(strval($xml_element)));
  153. }
  154. }
  155. /**
  156. * @brief Convert the given XML text to an array in the XML structure.
  157. *
  158. * xml::to_array() will convert the given XML text to an array in the XML structure.
  159. * Link: http://www.bin-co.com/php/scripts/xml2array/
  160. * Portions significantly re-written by mike@macgirvin.com for Friendica
  161. * (namespaces, lowercase tags, get_attribute default changed, more...)
  162. *
  163. * Examples: $array = xml::to_array(file_get_contents('feed.xml'));
  164. * $array = xml::to_array(file_get_contents('feed.xml', true, 1, 'attribute'));
  165. *
  166. * @param object $contents The XML text
  167. * @param boolean $namespaces True or false include namespace information
  168. * in the returned array as array elements.
  169. * @param integer $get_attributes 1 or 0. If this is 1 the function will get the attributes as well as the tag values -
  170. * this results in a different array structure in the return value.
  171. * @param string $priority Can be 'tag' or 'attribute'. This will change the way the resulting
  172. * array sturcture. For 'tag', the tags are given more importance.
  173. *
  174. * @return array The parsed XML in an array form. Use print_r() to see the resulting array structure.
  175. */
  176. public static function to_array($contents, $namespaces = true, $get_attributes=1, $priority = 'attribute') {
  177. if(!$contents) return array();
  178. if(!function_exists('xml_parser_create')) {
  179. logger('xml::to_array: parser function missing');
  180. return array();
  181. }
  182. libxml_use_internal_errors(true);
  183. libxml_clear_errors();
  184. if($namespaces)
  185. $parser = @xml_parser_create_ns("UTF-8",':');
  186. else
  187. $parser = @xml_parser_create();
  188. if(! $parser) {
  189. logger('xml::to_array: xml_parser_create: no resource');
  190. return array();
  191. }
  192. xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, "UTF-8");
  193. // http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
  194. xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
  195. xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
  196. @xml_parse_into_struct($parser, trim($contents), $xml_values);
  197. @xml_parser_free($parser);
  198. if(! $xml_values) {
  199. logger('xml::to_array: libxml: parse error: ' . $contents, LOGGER_DATA);
  200. foreach(libxml_get_errors() as $err)
  201. logger('libxml: parse: ' . $err->code . " at " . $err->line . ":" . $err->column . " : " . $err->message, LOGGER_DATA);
  202. libxml_clear_errors();
  203. return;
  204. }
  205. //Initializations
  206. $xml_array = array();
  207. $parents = array();
  208. $opened_tags = array();
  209. $arr = array();
  210. $current = &$xml_array; // Reference
  211. // Go through the tags.
  212. $repeated_tag_index = array(); // Multiple tags with same name will be turned into an array
  213. foreach($xml_values as $data) {
  214. unset($attributes,$value); // Remove existing values, or there will be trouble
  215. // This command will extract these variables into the foreach scope
  216. // tag(string), type(string), level(int), attributes(array).
  217. extract($data); // We could use the array by itself, but this cooler.
  218. $result = array();
  219. $attributes_data = array();
  220. if(isset($value)) {
  221. if($priority == 'tag') $result = $value;
  222. else $result['value'] = $value; // Put the value in a assoc array if we are in the 'Attribute' mode
  223. }
  224. //Set the attributes too.
  225. if(isset($attributes) and $get_attributes) {
  226. foreach($attributes as $attr => $val) {
  227. if($priority == 'tag') $attributes_data[$attr] = $val;
  228. else $result['@attributes'][$attr] = $val; // Set all the attributes in a array called 'attr'
  229. }
  230. }
  231. // See tag status and do the needed.
  232. if($namespaces && strpos($tag,':')) {
  233. $namespc = substr($tag,0,strrpos($tag,':'));
  234. $tag = strtolower(substr($tag,strlen($namespc)+1));
  235. $result['@namespace'] = $namespc;
  236. }
  237. $tag = strtolower($tag);
  238. if($type == "open") { // The starting of the tag '<tag>'
  239. $parent[$level-1] = &$current;
  240. if(!is_array($current) or (!in_array($tag, array_keys($current)))) { // Insert New tag
  241. $current[$tag] = $result;
  242. if($attributes_data) $current[$tag. '_attr'] = $attributes_data;
  243. $repeated_tag_index[$tag.'_'.$level] = 1;
  244. $current = &$current[$tag];
  245. } else { // There was another element with the same tag name
  246. if(isset($current[$tag][0])) { // If there is a 0th element it is already an array
  247. $current[$tag][$repeated_tag_index[$tag.'_'.$level]] = $result;
  248. $repeated_tag_index[$tag.'_'.$level]++;
  249. } else { // This section will make the value an array if multiple tags with the same name appear together
  250. $current[$tag] = array($current[$tag],$result); // This will combine the existing item and the new item together to make an array
  251. $repeated_tag_index[$tag.'_'.$level] = 2;
  252. if(isset($current[$tag.'_attr'])) { // The attribute of the last(0th) tag must be moved as well
  253. $current[$tag]['0_attr'] = $current[$tag.'_attr'];
  254. unset($current[$tag.'_attr']);
  255. }
  256. }
  257. $last_item_index = $repeated_tag_index[$tag.'_'.$level]-1;
  258. $current = &$current[$tag][$last_item_index];
  259. }
  260. } elseif($type == "complete") { // Tags that ends in 1 line '<tag />'
  261. //See if the key is already taken.
  262. if(!isset($current[$tag])) { //New Key
  263. $current[$tag] = $result;
  264. $repeated_tag_index[$tag.'_'.$level] = 1;
  265. if($priority == 'tag' and $attributes_data) $current[$tag. '_attr'] = $attributes_data;
  266. } else { // If taken, put all things inside a list(array)
  267. if(isset($current[$tag][0]) and is_array($current[$tag])) { // If it is already an array...
  268. // ...push the new element into that array.
  269. $current[$tag][$repeated_tag_index[$tag.'_'.$level]] = $result;
  270. if($priority == 'tag' and $get_attributes and $attributes_data) {
  271. $current[$tag][$repeated_tag_index[$tag.'_'.$level] . '_attr'] = $attributes_data;
  272. }
  273. $repeated_tag_index[$tag.'_'.$level]++;
  274. } else { // If it is not an array...
  275. $current[$tag] = array($current[$tag],$result); //...Make it an array using using the existing value and the new value
  276. $repeated_tag_index[$tag.'_'.$level] = 1;
  277. if($priority == 'tag' and $get_attributes) {
  278. if(isset($current[$tag.'_attr'])) { // The attribute of the last(0th) tag must be moved as well
  279. $current[$tag]['0_attr'] = $current[$tag.'_attr'];
  280. unset($current[$tag.'_attr']);
  281. }
  282. if($attributes_data) {
  283. $current[$tag][$repeated_tag_index[$tag.'_'.$level] . '_attr'] = $attributes_data;
  284. }
  285. }
  286. $repeated_tag_index[$tag.'_'.$level]++; // 0 and 1 indexes are already taken
  287. }
  288. }
  289. } elseif($type == 'close') { // End of tag '</tag>'
  290. $current = &$parent[$level-1];
  291. }
  292. }
  293. return($xml_array);
  294. }
  295. }
  296. ?>