Subversion Repositories php_utils

Rev

Rev 12 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * PHP SimpleXML-Supplement
  5.  * Copyright 2020 - 2021 Daniel Marschall, ViaThinkSoft
  6.  * Revision 2021-06-12
  7.  *
  8.  * Licensed under the Apache License, Version 2.0 (the "License");
  9.  * you may not use this file except in compliance with the License.
  10.  * You may obtain a copy of the License at
  11.  *
  12.  * http://www.apache.org/licenses/LICENSE-2.0
  13.  *
  14.  * Unless required by applicable law or agreed to in writing, software
  15.  * distributed under the License is distributed on an "AS IS" BASIS,
  16.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17.  * See the License for the specific language governing permissions and
  18.  * limitations under the License.
  19.  */
  20.  
  21.  
  22. // ======== ATTENTION, PLEASE READ ========
  23. // This supplement script was created to support rare PHP installations that
  24. // do not contain SimpleXML, for example at PHP you need to explicitly
  25. // install the package "php-xml" if you want to have SimpleXML (In the PHP
  26. // documentation, it is written that SimpleXML is available to all, which is
  27. // not true).
  28. //
  29. // Beware that the supplement behaves differently than the real SimpleXML!
  30. // (If you know how to improve this, please feel free to send me a patch)
  31. //
  32. // Just a few differences towards the original SimpleXML
  33. // - print_r() looks different
  34. // - The supplement requires that an XML string begins with "<!DOCTYPE" or "<?xml",
  35. //   otherwise, the first element will not be stripped away
  36. // - The supplement is slow because of regular expressions
  37. // - Many functions like "asXML" are not implemented
  38. // - There might be other incompatibilities
  39. //
  40. // So, if you want to use the SimpleXML supplement, then please carefully
  41. // test it with your application if it works.
  42. // ========================================
  43.  
  44. if (!function_exists('simplexml_load_string')) {
  45.  
  46.         // We cannot store the number 0, 1, 2, ... as items in the SimpleXMLElement, because PHP 7.0 had a bug
  47.         // that prevented /get_object_vars() from working correctly
  48.         // https://stackoverflow.com/questions/46000541/get-object-vars-returning-different-results-depending-on-php-version
  49.         // https://stackoverflow.com/questions/4914376/failed-to-get-dynamic-instance-variables-via-phps-reflection/4914405#comment76610293_4914405
  50.         define('SIMPLEXML_SUPPLEMENT_MAGIC', '_SIMPLEXML_SUPPLEMENT_IDX_');
  51.  
  52.         function _simplexml_supplement_isnumeric($x) {
  53.                 return substr($x,0,strlen(SIMPLEXML_SUPPLEMENT_MAGIC)) === SIMPLEXML_SUPPLEMENT_MAGIC;
  54.         }
  55.         function _simplexml_supplement_getnumber($x) {
  56.                 return (int)substr($x,strlen(SIMPLEXML_SUPPLEMENT_MAGIC));
  57.         }
  58.         function _simplexml_supplement_addnumberprefix($x) {
  59.                 return SIMPLEXML_SUPPLEMENT_MAGIC.$x;
  60.         }
  61.  
  62.         // We may not store the fields "position" and "attrs" in the SimpleXMLElement object,
  63.         // otherweise the typecast SimpleXMLElement=>array will include them
  64.         $_simplexml_supplement_properties = array();
  65.  
  66.         function simplexml_load_file($file): SimpleXMLElement {
  67.                 return simplexml_load_string(file_get_contents($file));
  68.         }
  69.  
  70.         function simplexml_load_string($testxml): SimpleXMLElement {
  71.                 $out = new SimpleXMLElement(); /** @phpstan-ignore-line */
  72.  
  73.                 $testxml = preg_replace('@<!\\-\\-.+\\-\\->@','',$testxml); // remove comments
  74.                 $testxml = preg_replace('@<([^>\\s]+)\\s*/>@smU','<\\1></\\1>',$testxml); // <x/> => <x></x>
  75.  
  76.                 if ((stripos($testxml, '<?xml') !== false) || (stripos($testxml, '<!doctype') !== false)) {
  77.                         $testxml = preg_replace('@<\\?.+\\?>@','',$testxml);
  78.                         $testxml = preg_replace('@<!doctype.+>@i','',$testxml);
  79.                         $m = array();
  80.                         preg_match('@<(\\S+?)[^>]*>(.*)</\\1>@smU',$testxml,$m); // find root element
  81.                         $root_element = $m[1];
  82.                 } else {
  83.                         $root_element = null;
  84.                 }
  85.  
  86.                 $m = array();
  87.                 preg_match_all('@<(\\S+?)([^>]*)>(.*)</\\1>@smU', $testxml, $m, PREG_SET_ORDER);
  88.                 foreach ($m as $n) {
  89.                         $name = $n[1];
  90.                         $other = $n[2];
  91.                         $val  = $n[3];
  92.  
  93.                         // We are using chr(1) to avoid that <x> is parsed as child if it follows CDATA immediately
  94.                         $val = str_replace('<![CDATA[', chr(1), $val);
  95.                         $val = str_replace(']]>', chr(1), $val);
  96.                         $val = trim($val);
  97.  
  98.                         $new = $out->addChild($name, $val);
  99.  
  100.                         $m2 = array();
  101.                         preg_match_all('@(\S+)=\\"([^\\"]+)\\"@smU', $other, $m2, PREG_SET_ORDER);
  102.                         foreach ($m2 as $n2) {
  103.                                 $att_name = $n2[1];
  104.                                 $att_val = $n2[2];
  105.                                 $new->addAttribute($att_name, $att_val);
  106.                         }
  107.                 }
  108.  
  109.                 if (!is_null($root_element)) {
  110.                         $out = $out->$root_element;
  111.                 }
  112.  
  113.                 return $out;
  114.         }
  115.  
  116.         class SimpleXMLElement implements ArrayAccess, Iterator { /** @phpstan-ignore-line */
  117.  
  118.                 function __destruct() {
  119.                         global $_simplexml_supplement_properties;
  120.                         unset($_simplexml_supplement_properties[spl_object_hash($this)]);
  121.                 }
  122.  
  123.                 public function addAttribute($name, $val) {
  124.                         global $_simplexml_supplement_properties;
  125.                         $_simplexml_supplement_properties[spl_object_hash($this)]['attrs'][$name] = $val;
  126.                 }
  127.  
  128.                 public function attributes() {
  129.                         global $_simplexml_supplement_properties;
  130.                         return $_simplexml_supplement_properties[spl_object_hash($this)]['attrs'];
  131.                 }
  132.  
  133.                 public function isSupplement() {
  134.                         return true;
  135.                 }
  136.  
  137.                 public function __construct($val=null) {
  138.                         global $_simplexml_supplement_properties;
  139.                         $_simplexml_supplement_properties[spl_object_hash($this)] = array(
  140.                                 "position" => 0,
  141.                                 "attrs" => array()
  142.                         );
  143.                         if (!is_null($val)) {
  144.                                 $this->{_simplexml_supplement_addnumberprefix(0)} = $val;
  145.                         }
  146.                 }
  147.  
  148.                 public function isArray() {
  149.                         $vars = get_object_vars($this);
  150.                         $max = -1;
  151.                         foreach ($vars as $x => $dummy) {
  152.                                 if (!_simplexml_supplement_isnumeric($x)) {
  153.                                         $max = -1;
  154.                                         break;
  155.                                 } else {
  156.                                         $num = _simplexml_supplement_getnumber($x);
  157.                                         if ($num > $max) $max = $num;
  158.                                 }
  159.                         }
  160.                         return $max > 0;
  161.                 }
  162.  
  163.                 public function addToArray($val) {
  164.                         $vars = get_object_vars($this);
  165.                         $max = -1;
  166.                         foreach ($vars as $x => $dummy) {
  167.                                 if (!_simplexml_supplement_isnumeric($x)) {
  168.                                         $max = -1;
  169.                                         break;
  170.                                 } else {
  171.                                         $num = _simplexml_supplement_getnumber($x);
  172.                                         if ($num > $max) $max = $num;
  173.                                 }
  174.                         }
  175.                         $max++;
  176.                         $this->{_simplexml_supplement_addnumberprefix($max)} = $val;
  177.                 }
  178.  
  179.                 public function __toString() {
  180.                         $data = get_object_vars($this);
  181.                         if (is_array($data)) {
  182.                                 if (isset($data[_simplexml_supplement_addnumberprefix(0)])) {
  183.                                         return $data[_simplexml_supplement_addnumberprefix(0)];
  184.                                 } else {
  185.                                         return '';
  186.                                 }
  187.                         } else { /** @phpstan-ignore-line */
  188.                                 return $data;
  189.                         }
  190.                 }
  191.  
  192.                 public function offsetExists($offset) {
  193.                         return isset($this->$offset);
  194.                 }
  195.  
  196.                 public function offsetGet($offset) {
  197.                         return $this->$offset;
  198.                 }
  199.  
  200.                 public function offsetSet($offset, $value) {
  201.                         $this->$offset = $value;
  202.                 }
  203.  
  204.                 public function offsetUnset($offset) {
  205.                         unset($this->$offset);
  206.                 }
  207.  
  208.                 public function __get($name) {
  209.                         // Output nothing
  210.                         return new SimpleXMLElement(); /** @phpstan-ignore-line */
  211.                 }
  212.  
  213.                 public function addChild($name, $val=null) {
  214.                         global $_simplexml_supplement_properties;
  215.  
  216.                         if ($val == null) $val = new SimpleXMLElement(); /** @phpstan-ignore-line */
  217.  
  218.                         if ((substr(trim($val),0,1) === '<') || (trim($val) == '')) {
  219.                                 $val = simplexml_load_string($val);
  220.                         }
  221.  
  222.                         if (is_string($val)) $val = str_replace(chr(1), '', $val);
  223.  
  224.                         $data = get_object_vars($this);
  225.  
  226.                         if (!isset($data[$name])) {
  227.                                 if ($val instanceof SimpleXMLElement) {
  228.                                         $this->$name = $val;
  229.                                 } else {
  230.                                         $this->$name = new SimpleXMLElement($val);
  231.                                 }
  232.                         } else {
  233.                                 if (!($val instanceof SimpleXMLElement)) {
  234.                                         $val = new SimpleXMLElement($val);
  235.                                 }
  236.  
  237.                                 if ($data[$name]->isArray()) {
  238.                                         $data[$name]->addToArray($val);
  239.                                 } else {
  240.                                         $tmp = new SimpleXMLElement(); /** @phpstan-ignore-line */
  241.                                         $tmp->addToArray($data[$name]);
  242.                                         $tmp->addToArray($val);
  243.                                         $this->$name = $tmp;
  244.                                         $_simplexml_supplement_properties[spl_object_hash($this)]['attrs'] = array();
  245.                                 }
  246.                                 return $val;
  247.                         }
  248.  
  249.                         return $this->$name;
  250.                 }
  251.  
  252.                 public function rewind() {
  253.                         global $_simplexml_supplement_properties;
  254.                         $_simplexml_supplement_properties[spl_object_hash($this)]['position'] = 0;
  255.                 }
  256.  
  257.                 public function current() {
  258.                         global $_simplexml_supplement_properties;
  259.                         $vars = get_object_vars($this);
  260.                         $cnt = 0;
  261.                         foreach ($vars as $x => $dummy) { /** @phpstan-ignore-line */
  262.                                 if (($dummy instanceof SimpleXMLElement) && !_simplexml_supplement_isnumeric($x) && $dummy->isArray()) {
  263.                                         $vars2 = get_object_vars($dummy);
  264.                                         foreach ($vars2 as $x2 => $dummy2) {
  265.                                                 if ($cnt == $_simplexml_supplement_properties[spl_object_hash($this)]['position']) {
  266.                                                         if ($dummy2 instanceof SimpleXMLElement) {
  267.                                                                 return $dummy2; /** @phpstan-ignore-line */
  268.                                                         } else {
  269.                                                                 return new SimpleXMLElement($dummy2); /** @phpstan-ignore-line */
  270.                                                         }
  271.                                                 }
  272.                                                 $cnt++;
  273.                                         }
  274.                                 } else {
  275.                                         if ($cnt == $_simplexml_supplement_properties[spl_object_hash($this)]['position']) {
  276.                                                 if ($dummy instanceof SimpleXMLElement) {
  277.                                                         return $dummy; /** @phpstan-ignore-line */
  278.                                                 } else {
  279.                                                         return new SimpleXMLElement($dummy); /** @phpstan-ignore-line */
  280.                                                 }
  281.                                         }
  282.                                         $cnt++;
  283.                                 }
  284.                         }
  285.  
  286.  
  287.                 }
  288.  
  289.                 public function key() {
  290.                         global $_simplexml_supplement_properties;
  291.                         $vars = get_object_vars($this);
  292.                         $cnt = 0;
  293.                         foreach ($vars as $x => $dummy) { /** @phpstan-ignore-line */
  294.                                 if (($dummy instanceof SimpleXMLElement) && !_simplexml_supplement_isnumeric($x) && $dummy->isArray()) {
  295.                                         $vars2 = get_object_vars($dummy);
  296.                                         foreach ($vars2 as $x2 => $dummy2) {
  297.                                                 if ($cnt == $_simplexml_supplement_properties[spl_object_hash($this)]['position']) return $x/*sic*/;
  298.                                                 $cnt++;
  299.                                         }
  300.                                 } else {
  301.                                         if ($cnt == $_simplexml_supplement_properties[spl_object_hash($this)]['position']) return $x;
  302.                                         $cnt++;
  303.                                 }
  304.                         }
  305.                 }
  306.  
  307.                 public function next() {
  308.                         global $_simplexml_supplement_properties;
  309.                         ++$_simplexml_supplement_properties[spl_object_hash($this)]['position'];
  310.                 }
  311.  
  312.                 public function valid() {
  313.                         global $_simplexml_supplement_properties;
  314.  
  315.                         $vars = get_object_vars($this);
  316.                         $cnt = 0;
  317.                         foreach ($vars as $x => $dummy) {
  318.                                 if (($dummy instanceof SimpleXMLElement) && !_simplexml_supplement_isnumeric($x) && $dummy->isArray()) {
  319.                                         $vars2 = get_object_vars($dummy);
  320.                                         foreach ($vars2 as $x2 => $dummy2) {
  321.                                                 $cnt++;
  322.                                         }
  323.                                 } else {
  324.                                         $cnt++;
  325.                                 }
  326.                         }
  327.  
  328.                         return $_simplexml_supplement_properties[spl_object_hash($this)]['position'] < $cnt;
  329.                 }
  330.  
  331.         }
  332. }
  333.