3

I have an array that looks like this:

RecursiveArrayIterator {#605 ▼
  +"xs:schema": array:2 [▼
    "value" => array:1 [▼
      "xs:element" => array:2 [▼
        "value" => array:1 [▼
          "xs:complexType" => array:2 [▼
            "value" => array:2 [▼
              "xs:sequence" => array:2 [▼
                "value" => array:1 [▼
                  "xs:element" => array:3 [▼
                    0 => array:2 [▼
                      "value" => array:1 [▼
                        "xs:simpleType" => array:2 [▼
                          "value" => array:1 [▼
                            "xs:restriction" => array:2 [▼
                              "value" => array:1 [▼
                                "xs:maxLength" => array:1 [▼
                                  "attributes" => array:1 [▼
                                    "value" => "40"
                                  ]
                                ]
                              ]
                              "attributes" => array:1 [▶]
                            ]
                          ]
                          "attributes" => []
                        ]
                      ]
                      "attributes" => array:1 [▼
                        "name" => "title"
                      ]
                    ]
                    1 => array:2 [▶]
                    2 => array:2 [▶]
                  ]
                ]
                "attributes" => []
              ]
              "xs:attribute" => array:2 [▶]
            ]
            "attributes" => []
          ]
        ]
        "attributes" => array:1 [▼
          "name" => "book"
        ]
      ]
    ]
    "attributes" => []
  ]
}

I need to access the xs:maxLength attribute, so in order to that that, I'm using the following method:

private function findRestrictions(array $haystack, $needle)
{
    $iterator = new \RecursiveArrayIterator($haystack);
    $recursive = new \RecursiveIteratorIterator(
        $iterator,
        \RecursiveIteratorIterator::SELF_FIRST
    );

    foreach ($recursive as $key => $value)
    {
        if ($key === $needle)
        {
            return (int)$value['attributes']['value'];
        }
    }
}

$maxLength = findRestrictions($array, 'xs:maxLength');

So that gives me back 40, just like expected. Anyway, my issue is that I need to know to which element this limit belongs to, which is mentioned in xs:element[0]['attributes']['name'] and I'm uncertain on how to reach there to grab the information I need, based on the match for xs:maxLength.

halfer
  • 19,824
  • 17
  • 99
  • 186
aborted
  • 4,481
  • 14
  • 69
  • 132
  • 1
    This looks like **XML**, have you considered using **XPath**? It has the ability to get the element by some criteria (in your case it would be the max value). The performance might be not the best, but to be sure about that it should be tested against your iterator approach. – sevavietl Apr 24 '18 at 15:04
  • 1
    I can only echo @sevavietl's comment, it makes a lot of sense to use PHP's XML tools to navigate around your schema. [Here's a quick example I threw together using your schema to get the names of all elements with a maxLength restriction.](https://3v4l.org/KUFVJ) – salathe Apr 24 '18 at 22:42
  • @salathe can you post that as an answer so I can accept it? – aborted Apr 25 '18 at 09:39
  • @salathe Is there way to safely find the elements in the actual XML string so I can apply the max/min-length restrictions? This one seems rather tricky due to the fact that there could be many elements of the same tag in different levels on the XML, which might result on me applying the restriction to the wrong node. – aborted Apr 25 '18 at 15:21
  • 1
    @Aborted my comment isn't really an answer to the question, which explicitly asks about working with a multidimensional array. It would be more suited to a completely new question. As for finding elements that don't match the restrictions, this is starting to sound a lot like you're trying to validate an XML document against the schema and again [there are good tools to do that for you](https://3v4l.org/KJcWc). – salathe Apr 25 '18 at 18:01
  • @salathe Alright, I understand. And no, I don't need that exactly - I already have that in place. But I need to implement min/max-length specifically into the XML itself, instead of just validating it - as the source of the text that will be used as the value is unknown, therefore I don't want to strictly enforce min/max-length, but rather apply it. – aborted Apr 26 '18 at 08:08

2 Answers2

2

Well I have programmed a pretty good solution I think, this time it is tested.

My sample array:

$array = [
        "we" => 
            ["are" => [
                "lorem" => [
                    "gone" => "away",
                    "my" => "friend"
                    ],
                "never" => "abcd",
                "any" => [
                        "btc" => "abc",
                        "help" => [
                            "mqf" => "bmx"
                            ]
                    ]
                ]
            ],
         "fancy" => [
                "lorem" => [
                    "gone" => "away",
                    "my" => "friend"
                    ],
                "never" => "abcd",
                "any" => [
                        "btc" => "abc",
                        "help" => [
                            "mqf" => "bmx",
                            "abc" => 13
                            ]
                    ]
                ],
         "beer" => "bar",
         "helpful" => [
                "meta" => "column",
                "gamma" => [
                    "lorem" => [
                        "gone" => "mad",
                        "my" => "drink"
                        ],
                    "never" => "abcd",
                    "any" => [
                            "btc" => "abc",
                            "help" => [
                                "mqf" => "bmx",
                                "abc" => "alot"
                                ]
                        ]
                    ]
             ],
          "elements" => [
                0 => 88,
                1 => 99
              ]
        ];

My solution:

    function array_find_value_return_parent($array,$needle,$parentkey) {
        $iterator = new RecursiveIteratorIterator(
            new RecursiveArrayIterator($array),
            RecursiveIteratorIterator::SELF_FIRST);

        foreach($iterator as $key => $value) {
            if($value === $needle) {
                for ($i = $iterator->getDepth() - 1; $i >= 0; $i--) {
                    if($iterator->getSubIterator($i)->key() === $parentkey) {
                        return $iterator->getSubIterator($i)->current();
                    }
                }
            }
        }
    }

    function array_find_key_return_value($array,$findkey) {
        $iterator = new RecursiveIteratorIterator(
            new RecursiveArrayIterator($array),
            RecursiveIteratorIterator::SELF_FIRST);

        foreach($iterator as $key => $value) {
            if($findkey === $key) {
               return $iterator->current();
            }
        }
    }

My test:

    $findvalue = "alot";
    $findparentkey = "gamma";
    $findreturnkey = "gone";

   echo array_find_key_return_value(array_find_value_return_parent($array,$findvalue,$findparentkey),$findreturnkey);

Output: mad

For your case it means that you might do the following:

    $findvalue = "40";
    $findparentkey = "xs:element";
    $findreturnkey = "name";

   echo array_find_key_return_value(array_find_value_return_parent($array,$findvalue,$findparentkey),$findreturnkey);

Expected output: title

Right?

Blackbam
  • 17,496
  • 26
  • 97
  • 150
0

I do not know your original data structure, so I just converted your data to a PHP array. You can use $aks = new ArrayKeySearcher($data, 'xs:maxLength'); to find the key you want. And you can make the search more complex to satisfy your requirement.

However, if you are using something like XML, it is highly recommended to use XML-based solutions, like XPath query (eg: http://php.net/manual/en/domxpath.query.php, http://php.net/manual/en/simplexmlelement.xpath.php). These methods are easier to use, faster and more accurate.

<?php

$data =  [
"xs:schema"=>  [
    "value" =>  [
      "xs:element" =>  [
        "value" =>  [
          "xs:complexType" =>  [
            "value" =>  [
              "xs:sequence" =>  [
                "value" =>  [
                  "xs:element" =>  [
                    0 =>  [
                      "value" =>  [
                        "xs:simpleType" =>  [
                          "value" =>  [
                            "xs:restriction" =>  [
                              "value" =>  [
                                "xs:maxLength" =>  [
                                  "attributes" =>  [
                                    "value" => "40"
                                  ]
                                ]
                              ],
                              "attributes" =>  []
                            ]
                          ],
                          "attributes" => []
                        ]
                      ],
                      "attributes" =>  [
                        "name" => "title"
                      ]
                    ],
                    1 =>  [],
                    2 =>  [],
                  ]
                ],
                "attributes" => []
              ],
              "xs:attribute" =>  []
            ],
            "attributes" => []
          ]
        ],
        "attributes" =>  [
          "name" => "book"
        ]
      ]
    ],
    "attributes" => []
  ]
];


class ArrayKeySearcher
{
    public $data;
    public $path;
    public $value;

    public function __construct($data, $key)
    {
        $this->data = $data;
        $this->findKeyPath($data, $key);
    }

    private function findKeyPath($data, $key)
    {
        foreach ($data as $k => $v) {
            $this->path[] = $k;
            if ($key === $k) {
                $this->value = $v;
                return;
            }
            $this->findKeyPath($v, $key);
            if (!is_null($this->value))
                return;
            array_pop($this->path);
        }
    }

    public function arrayReverseSearch($a, $k, $pos = null)
    {
        $count = count($a);
        $i = ($pos === null) ? ($count - 1) : $pos;
        for(; $i >= 0; $i--) {
            if($a[$i] === $k)
                return $i;
        }
        return $i;
    }

    public function getValueByPath($path)
    {
        $v = $this->data;
        foreach($path as $k) {
            if(isset($v[$k]))
                $v = $v[$k];
        }
        return $v;
    }
}


$aks = new ArrayKeySearcher($data, 'xs:maxLength');
echo 'path: ' . json_encode($aks->path) . PHP_EOL;
echo 'value: ' . json_encode($aks->value) . PHP_EOL;

$p = $aks->path;
$pos = $aks->arrayReverseSearch($p, 'xs:simpleType');
$pos = $aks->arrayReverseSearch($p, 'value', $pos);
$p = array_slice($p, 0, $pos);
$parent = $aks->getValueByPath($p);
echo 'parent path: ' . json_encode($p) . PHP_EOL;
echo 'parent attributes: ' . json_encode($parent['attributes']) . PHP_EOL;

Output:

path: ["xs:schema","value","xs:element","value","xs:complexType","value","xs:sequence","value","xs:element",0,"value","xs:simpleType","value","xs:restriction","value","xs:maxLength"]
value: {"attributes":{"value":"40"}}
parent path: ["xs:schema","value","xs:element","value","xs:complexType","value","xs:sequence","value","xs:element",0]
parent attributes: {"name":"title"}
shawn
  • 4,305
  • 1
  • 17
  • 25