Subversion Repositories php_utils

Rev

Go to most recent revision | Blame | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * PHP SimpleXML-Supplement
  5.  * Copyright 2020 - 2021 Daniel Marschall, ViaThinkSoft
  6.  * Revision 2021-05-25
  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.                         $val = str_replace('<![CDATA[', '', $val);
  94.                         $val = str_replace(']]>', '', $val);
  95.                         $val = trim($val);
  96.  
  97.                         $new = $out->addChild($name, $val);
  98.  
  99.                         $m2 = array();
  100.                         preg_match_all('@(\S+)=\\"([^\\"]+)\\"@smU', $other, $m2, PREG_SET_ORDER);
  101.                         foreach ($m2 as $n2) {
  102.                                 $att_name = $n2[1];
  103.                                 $att_val = $n2[2];
  104.                                 $new->addAttribute($att_name, $att_val);
  105.                         }
  106.                 }
  107.  
  108.                 if (!is_null($root_element)) {
  109.                         $out = $out->$root_element;
  110.                 }
  111.  
  112.                 return $out;
  113.         }
  114.  
  115.         class SimpleXMLElement implements ArrayAccess, Iterator {
  116.  
  117.                 function __destruct() {
  118.                         global $_simplexml_supplement_properties;
  119.                         unset($_simplexml_supplement_properties[spl_object_hash($this)]);
  120.                 }
  121.  
  122.                 public function addAttribute($name, $val) {
  123.                         global $_simplexml_supplement_properties;
  124.                         $_simplexml_supplement_properties[spl_object_hash($this)]['attrs'][$name] = $val;
  125.                 }
  126.  
  127.                 public function attributes() {
  128.                         global $_simplexml_supplement_properties;
  129.                         return $_simplexml_supplement_properties[spl_object_hash($this)]['attrs'];
  130.                 }
  131.  
  132.                 public function isSupplement() {
  133.                         return true;
  134.                 }
  135.  
  136.                 public function __construct($val=null) {
  137.                         global $_simplexml_supplement_properties;
  138.                         $_simplexml_supplement_properties[spl_object_hash($this)] = array(
  139.                                 "position" => 0,
  140.                                 "attrs" => array()
  141.                         );
  142.                         if (!is_null($val)) {
  143.                                 $this->{_simplexml_supplement_addnumberprefix(0)} = $val;
  144.                         }
  145.                 }
  146.  
  147.                 public function isArray() {
  148.                         $vars = get_object_vars($this);
  149.                         $max = -1;
  150.                         foreach ($vars as $x => $dummy) {
  151.                                 if (!_simplexml_supplement_isnumeric($x)) {
  152.                                         $max = -1;
  153.                                         break;
  154.                                 } else {
  155.                                         $num = _simplexml_supplement_getnumber($x);
  156.                                         if ($num > $max) $max = $num;
  157.                                 }
  158.                         }
  159.                         return $max > 0;
  160.                 }
  161.  
  162.                 public function addToArray($val) {
  163.                         $vars = get_object_vars($this);
  164.                         $max = -1;
  165.                         foreach ($vars as $x => $dummy) {
  166.                                 if (!_simplexml_supplement_isnumeric($x)) {
  167.                                         $max = -1;
  168.                                         break;
  169.                                 } else {
  170.                                         $num = _simplexml_supplement_getnumber($x);
  171.                                         if ($num > $max) $max = $num;
  172.                                 }
  173.                         }
  174.                         $max++;
  175.                         $this->{_simplexml_supplement_addnumberprefix($max)} = $val;
  176.                 }
  177.  
  178.                 public function __toString() {
  179.                         $data = get_object_vars($this);
  180.                         if (is_array($data)) {
  181.                                 if (isset($data[_simplexml_supplement_addnumberprefix(0)])) {
  182.                                         return $data[_simplexml_supplement_addnumberprefix(0)];
  183.                                 } else {
  184.                                         return '';
  185.                                 }
  186.                         } else {
  187.                                 return $data;
  188.                         }
  189.                 }
  190.  
  191.                 public function offsetExists($offset) {
  192.                         return isset($this->$offset);
  193.                 }
  194.  
  195.                 public function offsetGet($offset) {
  196.                         return $this->$offset;
  197.                 }
  198.  
  199.                 public function offsetSet($offset, $value) {
  200.                         $this->$offset = $value;
  201.                 }
  202.  
  203.                 public function offsetUnset($offset) {
  204.                         unset($this->$offset);
  205.                 }
  206.  
  207.                 public function __get($name) {
  208.                         // Output nothing
  209.                         return new SimpleXMLElement(); /** @phpstan-ignore-line */
  210.                 }
  211.  
  212.                 public function addChild($name, $val=null) {
  213.                         global $_simplexml_supplement_properties;
  214.  
  215.                         if ($val == null) $val = new SimpleXMLElement(); /** @phpstan-ignore-line */
  216.  
  217.                         if ((substr(trim($val),0,1) === '<') || (trim($val) == '')) {
  218.                                 $val = simplexml_load_string($val);
  219.                         }
  220.  
  221.                         $data = get_object_vars($this);
  222.  
  223.                         if (!isset($data[$name])) {
  224.                                 if ($val instanceof SimpleXMLElement) {
  225.                                         $this->$name = $val;
  226.                                 } else {
  227.                                         $this->$name = new SimpleXMLElement($val);
  228.                                 }
  229.                         } else {
  230.                                 if (!($val instanceof SimpleXMLElement)) {
  231.                                         $val = new SimpleXMLElement($val);
  232.                                 }
  233.  
  234.                                 if ($data[$name]->isArray()) {
  235.                                         $data[$name]->addToArray($val);
  236.                                 } else {
  237.                                         $tmp = new SimpleXMLElement(); /** @phpstan-ignore-line */
  238.                                         $tmp->addToArray($data[$name]);
  239.                                         $tmp->addToArray($val);
  240.                                         $this->$name = $tmp;
  241.                                         $_simplexml_supplement_properties[spl_object_hash($this)]['attrs'] = array();
  242.                                 }
  243.                                 return $val;
  244.                         }
  245.  
  246.                         return $this->$name;
  247.                 }
  248.  
  249.                 public function rewind() {
  250.                         global $_simplexml_supplement_properties;
  251.                         $_simplexml_supplement_properties[spl_object_hash($this)]['position'] = 0;
  252.                 }
  253.  
  254.                 public function current() {
  255.                         global $_simplexml_supplement_properties;
  256.                         $vars = get_object_vars($this);
  257.                         $cnt = 0;
  258.                         foreach ($vars as $x => $dummy) {
  259.                                 if (($dummy instanceof SimpleXMLElement) && !_simplexml_supplement_isnumeric($x) && $dummy->isArray()) {
  260.                                         $vars2 = get_object_vars($dummy);
  261.                                         foreach ($vars2 as $x2 => $dummy2) {
  262.                                                 if ($cnt == $_simplexml_supplement_properties[spl_object_hash($this)]['position']) {
  263.                                                         if ($dummy2 instanceof SimpleXMLElement) {
  264.                                                                 return $dummy2;
  265.                                                         } else {
  266.                                                                 return new SimpleXMLElement($dummy2);
  267.                                                         }
  268.                                                 }
  269.                                                 $cnt++;
  270.                                         }
  271.                                 } else {
  272.                                         if ($cnt == $_simplexml_supplement_properties[spl_object_hash($this)]['position']) {
  273.                                                 if ($dummy instanceof SimpleXMLElement) {
  274.                                                         return $dummy;
  275.                                                 } else {
  276.                                                         return new SimpleXMLElement($dummy);
  277.                                                 }
  278.                                         }
  279.                                         $cnt++;
  280.                                 }
  281.                         }
  282.  
  283.  
  284.                 }
  285.  
  286.                 public function key() {
  287.                         global $_simplexml_supplement_properties;
  288.                         $vars = get_object_vars($this);
  289.                         $cnt = 0;
  290.                         foreach ($vars as $x => $dummy) {
  291.                                 if (($dummy instanceof SimpleXMLElement) && !_simplexml_supplement_isnumeric($x) && $dummy->isArray()) {
  292.                                         $vars2 = get_object_vars($dummy);
  293.                                         foreach ($vars2 as $x2 => $dummy2) {
  294.                                                 if ($cnt == $_simplexml_supplement_properties[spl_object_hash($this)]['position']) return $x/*sic*/;
  295.                                                 $cnt++;
  296.                                         }
  297.                                 } else {
  298.                                         if ($cnt == $_simplexml_supplement_properties[spl_object_hash($this)]['position']) return $x;
  299.                                         $cnt++;
  300.                                 }
  301.                         }
  302.                 }
  303.  
  304.                 public function next() {
  305.                         global $_simplexml_supplement_properties;
  306.                         ++$_simplexml_supplement_properties[spl_object_hash($this)]['position'];
  307.                 }
  308.  
  309.                 public function valid() {
  310.                         global $_simplexml_supplement_properties;
  311.  
  312.                         $vars = get_object_vars($this);
  313.                         $cnt = 0;
  314.                         foreach ($vars as $x => $dummy) {
  315.                                 if (($dummy instanceof SimpleXMLElement) && !_simplexml_supplement_isnumeric($x) && $dummy->isArray()) {
  316.                                         $vars2 = get_object_vars($dummy);
  317.                                         foreach ($vars2 as $x2 => $dummy2) {
  318.                                                 $cnt++;
  319.                                         }
  320.                                 } else {
  321.                                         $cnt++;
  322.                                 }
  323.                         }
  324.  
  325.                         return $_simplexml_supplement_properties[spl_object_hash($this)]['position'] < $cnt;
  326.                 }
  327.  
  328.         }
  329. }
  330.